- added custom/editable ship jump logs, #709

- fixed DB setup error: "`system`.`description` can´t have a default value", closed #701
- upgraded "lazyload" js lib `v1.9.5` → `v1.9.7`
- upgraded multiple 3rd party NPM dependencies for Gulp build
This commit is contained in:
Mark Friedrich
2018-10-27 00:45:53 +02:00
parent 95222c309c
commit 07d5be71b2
95 changed files with 1766 additions and 560 deletions

View File

@@ -20,7 +20,6 @@ class AccessController extends Controller {
* @param $params
* @return bool
* @throws \Exception
* @throws \Exception\PathfinderException
* @throws \ZMQSocketException
*/
function beforeroute(\Base $f3, $params): bool {
@@ -29,15 +28,7 @@ class AccessController extends Controller {
// requires a valid logged in user!
if( !$this->isLoggedIn($f3) ){
// no character found or login timer expired
$this->logoutCharacter();
if($f3->get('AJAX')){
// unauthorized request
$f3->status(403);
}else{
// redirect to landing page
$f3->reroute(['login']);
}
$this->logoutCharacter($f3);
// skip route handler and afterroute()
$return = false;
}
@@ -51,7 +42,6 @@ class AccessController extends Controller {
* @param \Base $f3
* @return bool
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function isLoggedIn(\Base $f3): bool {
$loginCheck = false;
@@ -71,7 +61,6 @@ class AccessController extends Controller {
* @param \Base $f3
* @param Model\CharacterModel $character
* @return bool
* @throws \Exception\PathfinderException
*/
private function checkLogTimer(\Base $f3, Model\CharacterModel $character){
$loginCheck = false;
@@ -104,7 +93,6 @@ class AccessController extends Controller {
* @param Model\MapModel $map
* @return int (number of active connections for this map)
* @throws \Exception
* @throws \Exception\PathfinderException
* @throws \ZMQSocketException
*/
protected function broadcastMapData(Model\MapModel $map){
@@ -117,7 +105,6 @@ class AccessController extends Controller {
* @param Model\MapModel $map
* @return array
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function getFormattedMapData(Model\MapModel $map){
$mapData = $map->getData();

View File

@@ -37,7 +37,6 @@ class Admin extends Controller{
* @param $params
* @return bool
* @throws \Exception
* @throws \Exception\PathfinderException
*/
function beforeroute(\Base $f3, $params): bool {
$return = parent::beforeroute($f3, $params);
@@ -67,7 +66,6 @@ class Admin extends Controller{
/**
* event handler after routing
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
public function afterroute(\Base $f3) {
// js view (file)
@@ -123,7 +121,6 @@ class Admin extends Controller{
* @param $params
* @param null $character
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function dispatch(\Base $f3, $params, $character = null){
if($character instanceof CharacterModel){
@@ -232,7 +229,6 @@ class Admin extends Controller{
* @param CharacterModel $character
* @param int $kickCharacterId
* @param int $minutes
* @throws \Exception\PathfinderException
*/
protected function kickCharacter(CharacterModel $character, $kickCharacterId, $minutes){
$kickOptions = self::KICK_OPTIONS;
@@ -262,7 +258,6 @@ class Admin extends Controller{
* @param CharacterModel $character
* @param int $banCharacterId
* @param int $value
* @throws \Exception\PathfinderException
*/
protected function banCharacter(CharacterModel $character, $banCharacterId, $value){
$banCharacters = $this->filterValidCharacters($character, $banCharacterId);
@@ -309,7 +304,6 @@ class Admin extends Controller{
* @param CharacterModel $character
* @param int $mapId
* @param int $value
* @throws \Exception\PathfinderException
*/
protected function activateMap(CharacterModel $character, int $mapId, int $value){
$maps = $this->filterValidMaps($character, $mapId);
@@ -322,7 +316,6 @@ class Admin extends Controller{
/**
* @param CharacterModel $character
* @param int $mapId
* @throws \Exception\PathfinderException
*/
protected function deleteMap(CharacterModel $character, int $mapId){
$maps = $this->filterValidMaps($character, $mapId);
@@ -336,7 +329,6 @@ class Admin extends Controller{
* @param CharacterModel $character
* @param int $mapId
* @return \DB\CortexCollection[]|MapModel[]
* @throws \Exception\PathfinderException
*/
protected function filterValidMaps(CharacterModel $character, int $mapId) {
$maps = [];
@@ -355,7 +347,6 @@ class Admin extends Controller{
* get log file for "admin" logs
* @param string $type
* @return \Log
* @throws \Exception\PathfinderException
*/
static function getLogger($type = 'ADMIN'){
return parent::getLogger('ADMIN');
@@ -406,7 +397,6 @@ class Admin extends Controller{
* init /maps page data
* @param \Base $f3
* @param CharacterModel $character
* @throws \Exception\PathfinderException
*/
protected function initMaps(\Base $f3, CharacterModel $character){
$data = (object) [];

View File

@@ -7,6 +7,7 @@
*/
namespace Controller\Api;
use Controller;
use Model;
@@ -97,7 +98,7 @@ class Connection extends Controller\AccessController {
$map = Model\BasicModel::getNew('MapModel');
$map->getById($mapId);
if( $map->hasAccess($activeCharacter) ){
if($map->hasAccess($activeCharacter)){
foreach($connectionIds as $connectionId){
if( $connection = $map->getConnectionById($connectionId) ){
$connection->delete( $activeCharacter );

View File

@@ -29,7 +29,6 @@ class GitHub extends Controller\Controller {
/**
* get HTTP request options for API (curl) request
* @return array
* @throws \Exception\PathfinderException
*/
protected function getRequestReleaseOptions() : array {
$options = $this->getBaseRequestOptions();
@@ -41,7 +40,6 @@ class GitHub extends Controller\Controller {
* get HTTP request options for API (curl) request
* @param string $text
* @return array
* @throws \Exception\PathfinderException
*/
protected function getRequestMarkdownOptions(string $text) : array {
$params = [
@@ -59,7 +57,6 @@ class GitHub extends Controller\Controller {
/**
* get release information from GitHub
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
public function releases(\Base $f3){
$cacheKey = 'CACHE_GITHUB_RELEASES';

View File

@@ -7,6 +7,7 @@
*/
namespace Controller\Api;
use Controller;
use data\file\FileHandler;
use lib\Config;
@@ -50,7 +51,6 @@ class Map extends Controller\AccessController {
* Get all required static config data for program initialization
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function initData(\Base $f3){
// expire time in seconds
@@ -221,6 +221,7 @@ class Map extends Controller\AccessController {
// universe category data ---------------------------------------------------------------------------------
$return->universeCategories = [
6 => Model\Universe\BasicUniverseModel::getNew('CategoryModel')->getById(6)->getData(['mass']),
65 => Model\Universe\BasicUniverseModel::getNew('CategoryModel')->getById(65)->getData()
];
@@ -572,11 +573,7 @@ class Map extends Controller\AccessController {
$return->mapData = $map->getData();
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = $e->getField();
$validationError->message = $e->getMessage();
$return->error[] = $validationError;
$return->error[] = $e->getError();
}
}else{
// map access denied
@@ -634,7 +631,6 @@ class Map extends Controller\AccessController {
* -> if characters with map access found -> broadcast mapData to them
* @param Model\MapModel $map
* @throws Exception
* @throws Exception\PathfinderException
* @throws \ZMQSocketException
*/
protected function broadcastMapAccess(Model\MapModel $map){
@@ -708,7 +704,6 @@ class Map extends Controller\AccessController {
* -> function is called continuously (trigger) by any active client
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function updateData(\Base $f3){
$postData = (array)$f3->get('POST');
@@ -848,7 +843,6 @@ class Map extends Controller\AccessController {
* @param Model\MapModel[] $mapModels
* @return array
* @throws Exception
* @throws Exception\PathfinderException
*/
protected function getFormattedMapsData($mapModels){
$mapData = [];
@@ -864,7 +858,6 @@ class Map extends Controller\AccessController {
* -> function is called continuously by any active client
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function updateUserData(\Base $f3){
$postData = (array)$f3->get('POST');
@@ -1196,7 +1189,6 @@ class Map extends Controller\AccessController {
* get map log data
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function getLogData(\Base $f3){
$postData = (array)$f3->get('POST');

View File

@@ -0,0 +1,45 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 13.10.2018
* Time: 16:14
*/
namespace Controller\Api\Rest;
use Controller;
abstract class AbstractRestController extends Controller\AccessController {
/**
* get send data from request
* API requests require "Content-Type: application/json"
* -> $_POST does not include request data -> request BODY might contain JSON
* @param \Base $f3
* @return array
*/
protected function getRequestData(\Base $f3) : array {
$data = [];
if( !empty($body = $f3->get('BODY')) ){
$bodyDecode = json_decode($body, true);
if(($jsonError = json_last_error()) === JSON_ERROR_NONE){
$data = $bodyDecode;
}else{
$f3->set('HALT', true);
$f3->error(400, 'Request data: ' . json_last_error_msg());
}
}
return $data;
}
/**
* render API response to client
* @param $output
*/
protected function out($output){
echo json_encode($output);
}
}

View File

@@ -0,0 +1,112 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 13.10.2018
* Time: 15:28
*/
namespace Controller\Api\Rest;
use Model;
class Log extends AbstractRestController {
/**
* put (insert) log data
* @param \Base $f3
* @throws \Exception
*/
public function put(\Base $f3){
$requestData = $this->getRequestData($f3);
$connectionData = [];
if($connectionId = (int)$requestData['connectionId']){
$activeCharacter = $this->getCharacter();
/**
* @var Model\ConnectionModel $connection
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
$connection->getById($connectionId);
if($connection->hasAccess($activeCharacter)){
$log = $connection->getNewLog();
$log->setData($requestData);
$log->record = false; // log not recorded by ESI
$log->save();
$connectionData[] = $log->getConnection()->getData(true, true);
}
}
$this->out($connectionData);
}
/**
* delete (deactivate) log data
* @param \Base $f3
* @param $params
* @throws \Exception
*/
public function delete(\Base $f3, $params){
$logId = (int)$params['id'];
$connectionData = [];
if($log = $this->update($logId, ['active' => false])){
$connectionData[] = $log->getConnection()->getData(true, true);
}
$this->out($connectionData);
}
/**
* update log data
* @param \Base $f3
* @param $params
* @throws \Exception
*/
public function patch(\Base $f3, $params){
$logId = (int)$params['id'];
$requestData = $this->getRequestData($f3);
$connectionData = [];
if($log = $this->update($logId, $requestData)){
$connectionData[] = $log->getConnection()->getData(true, true);
}
$this->out($connectionData);
}
// ----------------------------------------------------------------------------------------------------------------
/**
* update existing connectionLog with new data
* @param int $logId
* @param array $logData
* @return bool|Model\ConnectionLogModel
* @throws \Exception
*/
private function update(int $logId, array $logData){
$log = false;
if($logId){
$activeCharacter = $this->getCharacter();
/**
* @var Model\ConnectionLogModel $log
*/
$log = Model\BasicModel::getNew('ConnectionLogModel');
$log->getById($logId, 0, false);
if($log->hasAccess($activeCharacter)){
$log->setData($logData);
if(isset($logData['active'])){
$log->setActive((bool)$logData['active']);
}
$log->save();
}
}
return $log;
}
}

View File

@@ -414,7 +414,7 @@ class Route extends Controller\AccessController {
* @param array $mapIds
* @param array $filterData
* @return array
* @throws \Exception\PathfinderException
* @throws \Exception
*/
public function searchRoute(int $systemFromId, int $systemToId, $searchDepth = 0, array $mapIds = [], array $filterData = []) : array {
// search root by ESI API
@@ -439,7 +439,6 @@ class Route extends Controller\AccessController {
* @param array $filterData
* @return array
* @throws \Exception
* @throws \Exception\PathfinderException
*/
private function searchRouteCustom(int $systemFromId, int $systemToId, $searchDepth = 0, array $mapIds = [], array $filterData = []) : array {
// reset all previous set jump data
@@ -519,7 +518,6 @@ class Route extends Controller\AccessController {
* @param array $filterData
* @return array
* @throws \Exception
* @throws \Exception\PathfinderException
*/
private function searchRouteESI(int $systemFromId, int $systemToId, int $searchDepth = 0, array $mapIds = [], array $filterData = []) : array {
// reset all previous set jump data
@@ -645,7 +643,6 @@ class Route extends Controller\AccessController {
* search multiple route between two systems
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function search($f3){
$requestData = (array)$f3->get('POST');

View File

@@ -123,7 +123,6 @@ class Statistic extends Controller\AccessController {
* @param int $yearEnd
* @param int $weekEnd
* @return array
* @throws \Exception\PathfinderException
*/
protected function queryStatistic( CharacterModel $character, $typeId, $yearStart, $weekStart, $yearEnd, $weekEnd){
$data = [];

View File

@@ -100,14 +100,8 @@ class System extends Controller\AccessController {
$return->error = $systemModel->getErrors();
}
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = $e->getField();
$validationError->message = $e->getMessage();
$return->error[] = $validationError;
$return->error[] = $e->getError();
}
}
}
@@ -246,7 +240,6 @@ class System extends Controller\AccessController {
* send Rally Point poke
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function pokeRally(\Base $f3){
$rallyData = (array)$f3->get('POST');

View File

@@ -109,7 +109,6 @@ class User extends Controller\Controller{
* -> return character data (if valid)
* @param \Base $f3
* @throws Exception
* @throws Exception\PathfinderException
*/
public function getCookieCharacter(\Base $f3){
$data = $f3->get('POST');
@@ -201,15 +200,10 @@ class User extends Controller\Controller{
/**
* log the current user out + clear character system log data
* @param \Base $f3
* @throws Exception
* @throws \ZMQSocketException
*/
public function logout(\Base $f3){
$this->logoutCharacter(false, true, true, true);
$return = (object) [];
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
echo json_encode($return);
$this->logoutCharacter($f3, false, true, true, true);
}
/**
@@ -345,17 +339,9 @@ class User extends Controller\Controller{
}
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = $e->getField();
$validationError->message = $e->getMessage();
$return->error[] = $validationError;
$return->error[] = $e->getError();
}catch(Exception\RegistrationException $e){
$registrationError = (object) [];
$registrationError->type = 'error';
$registrationError->field = $e->getField();
$registrationError->message = $e->getMessage();
$return->error[] = $registrationError;
$return->error[] = $e->getError();
}
// return new/updated user data
@@ -394,10 +380,8 @@ class User extends Controller\Controller{
sprintf(self::LOG_DELETE_ACCOUNT, $user->id, $user->name)
);
$this->logoutCharacter(true, true, true, true);
$this->logoutCharacter($f3, true, true, true, true);
$user->erase();
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
}
}else{
// captcha not valid -> return error

View File

@@ -52,7 +52,6 @@ class Sso extends Api\User{
* redirect user to CCP SSO page and request authorization
* -> cf. Controller->getCookieCharacters() ( equivalent cookie based login)
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
public function requestAdminAuthorization($f3){
// store browser tabId to be "targeted" after login
@@ -67,7 +66,6 @@ class Sso extends Api\User{
* -> cf. Controller->getCookieCharacters() ( equivalent cookie based login)
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function requestAuthorization($f3){
$params = $f3->get('GET');
@@ -133,7 +131,6 @@ class Sso extends Api\User{
* @param \Base $f3
* @param array $scopes
* @param string $rootAlias
* @throws \Exception\PathfinderException
*/
private function rerouteAuthorization(\Base $f3, $scopes = [], $rootAlias = 'login'){
if( !empty( Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') ) ){
@@ -166,7 +163,6 @@ class Sso extends Api\User{
* -> see requestAuthorization()
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function callbackAuthorization($f3){
$getParams = (array)$f3->get('GET');
@@ -307,7 +303,6 @@ class Sso extends Api\User{
* login by cookie name
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function login(\Base $f3){
$data = (array)$f3->get('GET');
@@ -345,7 +340,6 @@ class Sso extends Api\User{
* -> else try to refresh auth and get fresh "access_token"
* @param bool $authCode
* @return null|\stdClass
* @throws \Exception\PathfinderException
*/
public function getSsoAccessData($authCode){
$accessData = null;
@@ -365,7 +359,6 @@ class Sso extends Api\User{
* verify authorization code, and get an "access_token" data
* @param $authCode
* @return \stdClass
* @throws \Exception\PathfinderException
*/
protected function verifyAuthorizationCode($authCode){
$requestParams = [
@@ -381,7 +374,6 @@ class Sso extends Api\User{
* -> if "access_token" is expired, this function gets a fresh one
* @param $refreshToken
* @return \stdClass
* @throws \Exception\PathfinderException
*/
public function refreshAccessToken($refreshToken){
$requestParams = [
@@ -398,7 +390,6 @@ class Sso extends Api\User{
* OR by providing a valid "refresh_token"
* @param $requestParams
* @return \stdClass
* @throws \Exception\PathfinderException
*/
protected function requestAccessData($requestParams){
$verifyAuthCodeUrl = self::getVerifyAuthorizationCodeEndpoint();
@@ -463,7 +454,6 @@ class Sso extends Api\User{
* -> if more character information is required, use ESI "characters" endpoints request instead
* @param $accessToken
* @return mixed|null
* @throws \Exception\PathfinderException
*/
public function verifyCharacterData($accessToken){
$verifyUserUrl = self::getVerifyUserEndpoint();
@@ -586,7 +576,6 @@ class Sso extends Api\User{
* get CCP SSO url from configuration file
* -> throw error if url is broken/missing
* @return string
* @throws \Exception\PathfinderException
*/
static function getSsoUrlRoot(){
$url = '';
@@ -616,7 +605,6 @@ class Sso extends Api\User{
/**
* get logger for SSO logging
* @return \Log
* @throws \Exception\PathfinderException
*/
static function getSSOLogger(){
return parent::getLogger('SSO');

View File

@@ -9,6 +9,7 @@
namespace Controller;
use Controller\Api as Api;
use Exception\PathfinderException;
use lib\Config;
use lib\Resource;
use lib\Monolog;
@@ -66,7 +67,6 @@ class Controller {
* @param \Base $f3
* @param $params
* @return bool
* @throws \Exception\PathfinderException
*/
function beforeroute(\Base $f3, $params): bool {
// initiate DB connection
@@ -103,10 +103,10 @@ class Controller {
header($resource->buildHeader(), false);
}
if($this->getTemplate()){
if($file = $this->getTemplate()){
// Ajax calls don´t need a page render..
// this happens on client side
echo \Template::instance()->render( $this->getTemplate() );
echo \Template::instance()->render($file);
}
}
@@ -131,7 +131,6 @@ class Controller {
* @param $session
* @param $sid
* @return bool
* @throws \Exception\PathfinderException
*/
$onSuspect = function($session, $sid){
self::getLogger('SESSION_SUSPECT')->write( sprintf(
@@ -160,7 +159,6 @@ class Controller {
/**
* init new Resource handler
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
protected function initResource(\Base $f3){
$resource = Resource::instance();
@@ -228,7 +226,6 @@ class Controller {
* -> store validation data in DB
* @param Model\CharacterModel $character
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function setLoginCookie(Model\CharacterModel $character){
if( $this->getCookieState() ){
@@ -286,7 +283,6 @@ class Controller {
* @param bool $checkAuthorization
* @return Model\CharacterModel[]
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function getCookieCharacters($cookieData = [], $checkAuthorization = true){
$characters = [];
@@ -489,15 +485,16 @@ class Controller {
/**
* log out current character or all active characters (multiple browser tabs)
* -> send response data to client
* @param \Base $f3
* @param bool $all
* @param bool $deleteSession
* @param bool $deleteLog
* @param bool $deleteCookie
* @throws \Exception
* @throws \ZMQSocketException
*/
protected function logoutCharacter(bool $all = false, bool $deleteSession = true, bool $deleteLog = true, bool $deleteCookie = false){
$sessionCharacterData = (array)$this->getF3()->get(Api\User::SESSION_KEY_CHARACTERS);
protected function logoutCharacter(\Base $f3, bool $all = false, bool $deleteSession = true, bool $deleteLog = true, bool $deleteCookie = false){
$sessionCharacterData = (array)$f3->get(Api\User::SESSION_KEY_CHARACTERS);
if($sessionCharacterData){
$activeCharacterId = ($activeCharacter = $this->getCharacter()) ? $activeCharacter->_id : 0;
@@ -523,6 +520,20 @@ class Controller {
(new Socket( Config::getSocketUri() ))->sendData('characterLogout', $characterIds);
}
}
if($f3->get('AJAX')){
$status = 403;
$f3->status($status);
$return = (object) [];
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
$return->error[] = $this->getErrorObject($status, Config::getMessageFromHTTPStatus($status));
echo json_encode($return);
}else{
// redirect to landing page
$f3->reroute(['login']);
}
}
/**
@@ -627,7 +638,6 @@ class Controller {
/**
* get a custom userAgent string for API calls
* @return string
* @throws \Exception\PathfinderException
*/
protected function getUserAgent(){
$userAgent = '';
@@ -663,18 +673,26 @@ class Controller {
* -> on HTTP request -> render error page
* @param \Base $f3
* @return bool
* @throws \Exception\PathfinderException
*/
public function showError(\Base $f3){
if(!headers_sent()){
// collect error info -------------------------------------------------------------------------------------
$error = $this->getErrorObject(
$f3->get('ERROR.code'),
$f3->get('ERROR.status'),
$f3->get('ERROR.text'),
$f3->get('DEBUG') === 3 ? $f3->get('ERROR.trace') : null
);
$errorData = $f3->get('ERROR');
$exception = $f3->get('EXCEPTION');
if($exception instanceof PathfinderException){
// ... handle Pathfinder exceptions (e.g. validation Exceptions,..)
$error = $exception->getError();
}else{
// ... handle error $f3->error() calls
$error = $this->getErrorObject(
$errorData['code'],
$errorData['status'],
$errorData['text'],
$f3->get('DEBUG') === 3 ? $errorData['trace'] : null
);
}
// check if error is a PDO Exception ----------------------------------------------------------------------
if(strpos(strtolower( $f3->get('ERROR.text') ), 'duplicate') !== false){
@@ -725,24 +743,6 @@ class Controller {
* @return bool
*/
public function unload(\Base $f3){
// track some 4xx Client side errors
// 5xx errors are handled in "ONERROR" callback
$status = http_response_code();
if(!headers_sent() && $status >= 300){
if($f3->get('AJAX')){
$params = (array)$f3->get('POST');
$return = (object) [];
if((bool)$params['reroute']){
$return->reroute = rtrim(self::getEnvironmentData('URL'), '/') . $f3->alias('login');
}else{
// no reroute -> errors can be shown
$return->error[] = $this->getErrorObject($status, Config::getMessageFromHTTPStatus($status));
}
echo json_encode($return);
}
}
// store all user activities that are buffered for logging in this request
// this should work even on non HTTP200 responses
$this->logActivities();
@@ -887,7 +887,6 @@ class Controller {
* get the current registration status
* 0=registration stop |1=new registration allowed
* @return int
* @throws \Exception\PathfinderException
*/
static function getRegistrationStatus(){
return (int)Config::getPathfinderData('registration.status');
@@ -898,7 +897,6 @@ class Controller {
* -> set in pathfinder.ini
* @param string $type
* @return \Log|null
* @throws \Exception\PathfinderException
*/
static function getLogger($type){
return LogController::getLogger($type);

View File

@@ -163,7 +163,6 @@ class LogController extends \Prefab {
* get Logger instance
* @param string $type
* @return \Log|null
* @throws \Exception\PathfinderException
*/
public static function getLogger($type){
$logFiles = Config::getPathfinderData('logfiles');

View File

@@ -16,7 +16,6 @@ class MapController extends AccessController {
/**
* @param \Base $f3
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function init(\Base $f3) {
$character = $this->getCharacter();

View File

@@ -137,7 +137,6 @@ class Setup extends Controller {
* @param \Base $f3
* @param array $params
* @return bool
* @throws \Exception\PathfinderException
*/
function beforeroute(\Base $f3, $params): bool {
$this->initResource($f3);
@@ -162,7 +161,6 @@ class Setup extends Controller {
/**
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
public function afterroute(\Base $f3) {
// js view (file)
@@ -789,7 +787,6 @@ class Setup extends Controller {
* get default map config
* @param \Base $f3
* @return array
* @throws \Exception\PathfinderException
*/
protected function getMapsDefaultConfig(\Base $f3): array {
$matrix = \Matrix::instance();
@@ -1517,8 +1514,8 @@ class Setup extends Controller {
*/
protected function invalidateCookies(\Base $f3){
$this->getDB('PF');
$authentidationModel = Model\BasicModel::getNew('CharacterAuthenticationModel');
$results = $authentidationModel->find();
$authenticationModel = Model\BasicModel::getNew('CharacterAuthenticationModel');
$results = $authenticationModel->find();
if($results){
foreach($results as $result){
$result->erase();
@@ -1537,7 +1534,7 @@ class Setup extends Controller {
if($bytes){
$base = log($bytes, 1024);
$suffixes = array('', 'KB', 'M', 'GB', 'TB');
$result = round(pow(1024, $base - floor($base)), $precision) .''. $suffixes[floor($base)];
$result = round(pow(1024, $base - floor($base)), $precision) .''. $suffixes[(int)floor($base)];
}
return $result;
}

View File

@@ -22,7 +22,6 @@ class MapUpdate extends AbstractCron {
* deactivate all "private" maps whose lifetime is over
* >> php index.php "/cron/deactivateMapData"
* @param \Base $f3
* @throws \Exception\PathfinderException
*/
function deactivateMapData(\Base $f3){
$this->setMaxExecutionTime();

View File

@@ -116,7 +116,6 @@ class Database extends \Prefab {
* @param string $password
* @param string $alias
* @return SQL|null
* @throws \Exception\PathfinderException
*/
protected function connect($dns, $name, $user, $password, $alias){
$db = null;
@@ -286,7 +285,6 @@ class Database extends \Prefab {
/**
* get logger for DB logging
* @return \Log
* @throws \Exception\PathfinderException
*/
static function getLogger(){
return LogController::getLogger('ERROR');

View File

@@ -1,23 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: exodus4d
* Date: 21.02.15
* Time: 00:41
*/
namespace Exception;
class BaseException extends \Exception {
const VALIDATION_EXCEPTION = 403;
const REGISTRATION_EXCEPTION = 403;
const CONFIG_VALUE_EXCEPTION = 500;
const DB_EXCEPTION = 500;
public function __construct(string $message, int $code = 0){
parent::__construct($message, $code);
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 20.10.2018
* Time: 18:53
*/
namespace Exception;
class ConfigException extends PathfinderException {
protected $codes = [
1000 => 500
];
}

View File

@@ -8,9 +8,13 @@
namespace Exception;
class DatabaseException extends BaseException {
class DatabaseException extends PathfinderException {
protected $codes = [
1500 => 500
];
public function __construct(string $message){
parent::__construct($message, self::DB_EXCEPTION);
parent::__construct($message, 1500);
}
}

View File

@@ -1,17 +1,59 @@
<?php
/**
* Created by PhpStorm.
* User: exodu
* Date: 05.06.2017
* Time: 19:19
* User: exodus4d
* Date: 21.02.15
* Time: 00:41
*/
namespace Exception;
class PathfinderException extends BaseException{
class PathfinderException extends \Exception {
public function __construct($message){
parent::__construct($message, self::CONFIG_VALUE_EXCEPTION);
/**
* default HTTP response code for PathfinderExceptions
* -> can be specified by using custom Exception codes
*/
const DEFAULT_RESPONSECODE = 500;
/**
* lists all exception codes
* @var array
*/
protected $codes = [
0 => self::DEFAULT_RESPONSECODE
];
public function __construct(string $message, int $code = 0){
if( !array_key_exists($code, $this->codes) ){
// exception code not specified by child class
$code = 0;
}
parent::__construct($message, $code);
}
}
/**
* get error object
* @return \stdClass
*/
public function getError() : \stdClass {
$error = (object) [];
$error->type = 'error';
$error->code = $this->getResponseCode();
$error->status = @constant('Base::HTTP_' . $this->getResponseCode());
$error->message = $this->getMessage();
//$error->trace = $this->getTraceAsString();
$error->trace = $this->getTrace();
return $error;
}
/**
* returns the HTTP response code for the client from exception
* -> if Exception is not handled/catched 'somewhere' this code is used by the final onError handler
* @return int
*/
public function getResponseCode() : int {
return $this->codes[$this->getCode()];
}
}

View File

@@ -9,7 +9,11 @@
namespace Exception;
class RegistrationException extends BaseException{
class RegistrationException extends PathfinderException{
protected $codes = [
2000 => 403
];
/**
* form field name that causes this exception
@@ -17,22 +21,18 @@ class RegistrationException extends BaseException{
*/
private $field;
/**
* @return mixed
*/
public function getField(){
return $this->field;
}
/**
* @param mixed $field
*/
public function setField($field){
public function __construct(string $message, string $field = ''){
parent::__construct($message, 2000);
$this->field = $field;
}
public function __construct($message, $field = ''){
parent::__construct($message, self::REGISTRATION_EXCEPTION);
$this->setField($field);
/**
* get error object
* @return \stdClass
*/
public function getError() : \stdClass {
$error = parent::getError();
$error->field = $this->field;
return $error;
}
}

View File

@@ -9,7 +9,11 @@
namespace Exception;
class ValidationException extends BaseException {
class ValidationException extends PathfinderException {
protected $codes = [
2000 => 593
];
/**
* table column that triggers the exception
@@ -17,35 +21,18 @@ class ValidationException extends BaseException {
*/
private $field;
/**
* @return string
*/
public function getField(): string {
return $this->field;
}
/**
* @param string $field
*/
public function setField(string $field){
$this->field = $field;
}
public function __construct(string $message, string $field = ''){
parent::__construct($message, self::VALIDATION_EXCEPTION);
$this->setField($field);
parent::__construct($message, 2000);
$this->field = $field;
}
/**
* get error object
* @return \stdClass
*/
public function getError(){
$error = (object) [];
$error->type = 'error';
$error->field = $this->getField();
$error->message = $this->getMessage();
public function getError() : \stdClass {
$error = parent::getError();
$error->field = $this->field;
return $error;
}
}

View File

@@ -24,7 +24,6 @@ class CcpClient extends \Prefab {
* get ApiClient instance
* @param \Base $f3
* @return ApiClient|null
* @throws \Exception\PathfinderException
*/
protected function getClient(\Base $f3){
$client = null;
@@ -45,7 +44,6 @@ class CcpClient extends \Prefab {
/**
* @return string
* @throws \Exception\PathfinderException
*/
protected function getUserAgent(){
$userAgent = '';
@@ -71,7 +69,6 @@ class CcpClient extends \Prefab {
* @param $name
* @param $arguments
* @return array|mixed
* @throws \Exception\PathfinderException
*/
public function __call($name, $arguments){
$return = [];

View File

@@ -208,7 +208,6 @@ class Config extends \Prefab {
/**
* get SMTP config values
* @return \stdClass
* @throws Exception\PathfinderException
*/
static function getSMTPConfig(): \stdClass{
$config = new \stdClass();
@@ -253,7 +252,6 @@ class Config extends \Prefab {
* get email for notifications by hive key
* @param $key
* @return mixed
* @throws Exception\PathfinderException
*/
static function getNotificationMail($key){
return self::getPathfinderData('notification' . ($key ? '.' . $key : ''));
@@ -264,7 +262,6 @@ class Config extends \Prefab {
* -> read from pathfinder.ini
* @param string $mapType
* @return mixed
* @throws Exception\PathfinderException
*/
static function getMapsDefaultConfig($mapType = ''){
if( $mapConfig = self::getPathfinderData('map' . ($mapType ? '.' . $mapType : '')) ){
@@ -380,16 +377,15 @@ class Config extends \Prefab {
/**
* @param string $key
* @return null|mixed
* @throws Exception\PathfinderException
*/
static function getPathfinderData($key = ''){
$hiveKey = self::HIVE_KEY_PATHFINDER . ($key ? '.' . strtoupper($key) : '');
$data = null; // make sure it is always defined
try{
if( !\Base::instance()->exists($hiveKey, $data) ){
throw new Exception\PathfinderException(sprintf(self::ERROR_CONF_PATHFINDER, $hiveKey));
throw new Exception\ConfigException(sprintf(self::ERROR_CONF_PATHFINDER, $hiveKey));
}
}catch (Exception\PathfinderException $e){
}catch (Exception\ConfigException $e){
LogController::getLogger('ERROR')->write($e->getMessage());
}

View File

@@ -68,7 +68,6 @@ abstract class AbstractCharacterLog extends AbstractChannelLog{
/**
* get character thumbnailUrl
* @return string
* @throws \Exception\PathfinderException
*/
protected function getThumbUrl(): string {
$url = '';

View File

@@ -37,7 +37,6 @@ class RallyLog extends AbstractCharacterLog{
/**
* @return string
* @throws \Exception\PathfinderException
*/
protected function getThumbUrl() : string{
$url = '';

View File

@@ -179,7 +179,6 @@ abstract class AbstractSlackWebhookHandler extends Handler\AbstractProcessingHan
* @param array $attachment
* @param array $characterData
* @return array
* @throws \Exception\PathfinderException
*/
protected function setAuthor(array $attachment, array $characterData): array {
if( !empty($characterData['id']) && !empty($characterData['name'])){

View File

@@ -15,7 +15,6 @@ class SlackMapWebhookHandler extends AbstractSlackWebhookHandler {
/**
* @param array $record
* @return array
* @throws \Exception\PathfinderException
*/
protected function getSlackData(array $record) : array{
$postData = parent::getSlackData($record);

View File

@@ -15,7 +15,6 @@ class SlackRallyWebhookHandler extends AbstractSlackWebhookHandler {
/**
* @param array $record
* @return array
* @throws \Exception\PathfinderException
*/
protected function getSlackData(array $record) : array{
$postData = parent::getSlackData($record);

View File

@@ -100,7 +100,6 @@ class Web extends \Web {
* @param array $additionalOptions
* @param int $retryCount request counter for failed call
* @return array|FALSE|mixed
* @throws \Exception\PathfinderException
*/
public function request($url,array $options = null, $additionalOptions = [], $retryCount = 0 ) {
$f3 = \Base::instance();

View File

@@ -23,7 +23,7 @@ abstract class AbstractMapTrackingModel extends BasicModel implements LogModelIn
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'updatedCharacterId' => [
'type' => Schema::DT_INT,
@@ -35,7 +35,7 @@ abstract class AbstractMapTrackingModel extends BasicModel implements LogModelIn
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
]
];

View File

@@ -178,6 +178,7 @@ class ActivityLogModel extends BasicModel {
* @param null $table
* @param null $fields
* @return bool
* @throws \Exception
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);

View File

@@ -77,7 +77,6 @@ class AllianceModel extends BasicModel {
/**
* get all maps for this alliance
* @return array|mixed
* @throws \Exception\PathfinderException
*/
public function getMaps(){
$maps = [];

View File

@@ -118,7 +118,7 @@ abstract class BasicModel extends \DB\Cortex {
parent::__construct($db, $table, $fluid, $ttl);
// insert events ------------------------------------------------------------------------------------
$this->beforeinsert( function($self, $pkeys){
$this->beforeinsert(function($self, $pkeys){
return $self->beforeInsertEvent($self, $pkeys);
});
@@ -127,21 +127,21 @@ abstract class BasicModel extends \DB\Cortex {
});
// update events ------------------------------------------------------------------------------------
$this->beforeupdate( function($self, $pkeys){
$this->beforeupdate(function($self, $pkeys){
return $self->beforeUpdateEvent($self, $pkeys);
});
$this->afterupdate( function($self, $pkeys){
$this->afterupdate(function($self, $pkeys){
$self->afterUpdateEvent($self, $pkeys);
});
// erase events -------------------------------------------------------------------------------------
$this->beforeerase( function($self, $pkeys){
$this->beforeerase(function($self, $pkeys){
return $self->beforeEraseEvent($self, $pkeys);
});
$this->aftererase( function($self, $pkeys){
$this->aftererase(function($self, $pkeys){
$self->afterEraseEvent($self, $pkeys);
});
}
@@ -264,7 +264,7 @@ abstract class BasicModel extends \DB\Cortex {
* get static fields for this model instance
* @return array
*/
protected function getStaticFieldConf(): array {
protected function getStaticFieldConf() : array {
$staticFieldConfig = [];
// static tables (fixed data) do not require them...
@@ -299,13 +299,14 @@ abstract class BasicModel extends \DB\Cortex {
* @param $val
* @return bool
*/
protected function validateField(string $key, $val): bool {
protected function validateField(string $key, $val) : bool {
$valid = true;
if($fieldConf = $this->fieldConf[$key]){
if($method = $this->fieldConf[$key]['validate']){
if( !is_string($method)){
$method = 'validate_' . $key;
$method = $key;
}
$method = 'validate_' . $method;
if(method_exists($this, $method)){
// validate $key (column) with this method...
$valid = $this->$method($key, $val);
@@ -325,7 +326,7 @@ abstract class BasicModel extends \DB\Cortex {
* @return bool
* @throws \Exception\ValidationException
*/
protected function validate_notDry($key, $val): bool {
protected function validate_notDry($key, $val) : bool {
$valid = true;
if($colConf = $this->fieldConf[$key]){
if(isset($colConf['belongs-to-one'])){
@@ -344,6 +345,30 @@ abstract class BasicModel extends \DB\Cortex {
return $valid;
}
/**
* validates a model field to be not empty
* @param $key
* @param $val
* @return bool
*/
protected function validate_notEmpty($key, $val) : bool {
$valid = false;
if($colConf = $this->fieldConf[$key]){
switch($colConf['type']){
case Schema::DT_INT:
case Schema::DT_FLOAT:
if( (is_int($val) || ctype_digit($val)) && (int)$val > 0){
$valid = true;
}
break;
default:
}
}
return $valid;
}
/**
* get key for for all objects in this table
* @return string
@@ -628,7 +653,7 @@ abstract class BasicModel extends \DB\Cortex {
* function should be overwritten in parent classes
* @return bool
*/
public function isValid(): bool {
public function isValid() : bool {
return true;
}
@@ -776,7 +801,7 @@ abstract class BasicModel extends \DB\Cortex {
* @param string $action
* @return Logging\LogInterface
*/
protected function newLog($action = ''): Logging\LogInterface{
protected function newLog($action = '') : Logging\LogInterface{
return new Logging\DefaultLog($action);
}
@@ -800,7 +825,7 @@ abstract class BasicModel extends \DB\Cortex {
* get all validation errors
* @return array
*/
public function getErrors(): array {
public function getErrors() : array {
return $this->validationError;
}
@@ -808,7 +833,7 @@ abstract class BasicModel extends \DB\Cortex {
* checks whether data is outdated and should be refreshed
* @return bool
*/
protected function isOutdated(): bool {
protected function isOutdated() : bool {
$outdated = true;
if(!$this->dry()){
$timezone = $this->getF3()->get('getTimeZone')();
@@ -832,7 +857,7 @@ abstract class BasicModel extends \DB\Cortex {
}catch(ValidationException $e){
$this->setValidationError($e);
}catch(DatabaseException $e){
self::getF3()->error($e->getCode(), $e->getMessage(), $e->getTrace());
self::getF3()->error($e->getResponseCode(), $e->getMessage(), $e->getTrace());
}
}
@@ -913,7 +938,6 @@ abstract class BasicModel extends \DB\Cortex {
* debug log function
* @param string $text
* @param string $type
* @throws \Exception\PathfinderException
*/
public static function log($text, $type = 'DEBUG'){
Controller\LogController::getLogger($type)->write($text);

View File

@@ -473,7 +473,6 @@ class CharacterModel extends BasicModel {
* get ESI API "access_token" from OAuth
* @return bool|mixed
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function getAccessToken(){
$accessToken = false;
@@ -544,7 +543,6 @@ class CharacterModel extends BasicModel {
* checks whether this character is authorized to log in
* -> check corp/ally whitelist config (pathfinder.ini)
* @return bool
* @throws \Exception\PathfinderException
*/
public function isAuthorized(){
$authStatus = 'UNKNOWN';
@@ -614,7 +612,6 @@ class CharacterModel extends BasicModel {
* get Pathfinder role for character
* @return RoleModel
* @throws \Exception
* @throws \Exception\PathfinderException
*/
public function requestRole() : RoleModel{
$role = null;
@@ -660,7 +657,6 @@ class CharacterModel extends BasicModel {
* request all corporation roles granted to this character
* @return array
* @throws \Exception
* @throws \Exception\PathfinderException
*/
protected function requestRoles(){
$rolesData = [];
@@ -1021,7 +1017,6 @@ class CharacterModel extends BasicModel {
/**
* get all accessible map models for this character
* @return MapModel[]
* @throws \Exception\PathfinderException
*/
public function getMaps(){
$this->filter(

View File

@@ -30,11 +30,19 @@ class ConnectionLogModel extends BasicModel {
'table' => 'connection',
'on-delete' => 'CASCADE'
]
]
],
'validate' => 'notDry'
],
'record' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'shipTypeId' => [
'type' => Schema::DT_INT,
'index' => true
'index' => true,
'validate' => 'notEmpty'
],
'shipTypeName' => [
'type' => Schema::DT_VARCHAR128,
@@ -44,11 +52,13 @@ class ConnectionLogModel extends BasicModel {
'shipMass' => [
'type' => Schema::DT_FLOAT,
'nullable' => false,
'default' => 0
'default' => 0,
'validate' => 'notEmpty'
],
'characterId' => [
'type' => Schema::DT_INT,
'index' => true
'index' => true,
'validate' => 'notEmpty'
],
'characterName' => [
'type' => Schema::DT_VARCHAR128,
@@ -57,6 +67,14 @@ class ConnectionLogModel extends BasicModel {
]
];
/**
* set map data by an associative array
* @param array $data
*/
public function setData(array $data){
$this->copyfrom($data, ['shipTypeId', 'shipTypeName', 'shipMass', 'characterId', 'characterName']);
}
/**
* get connection log data
* @return \stdClass
@@ -64,6 +82,8 @@ class ConnectionLogModel extends BasicModel {
public function getData() : \stdClass {
$logData = (object) [];
$logData->id = $this->id;
$logData->active = $this->active;
$logData->record = $this->record;
$logData->connection = (object) [];
$logData->connection->id = $this->get('connectionId', true);
@@ -73,12 +93,46 @@ class ConnectionLogModel extends BasicModel {
$logData->ship->typeName = $this->shipTypeName;
$logData->ship->mass = $this->shipMass;
$logData->character = (object) [];
$logData->character->id = $this->characterId;
$logData->character->name = $this->characterName;
$logData->created = (object) [];
$logData->created->created = strtotime($this->created);
$logData->created->character = (object) [];
$logData->created->character->id = $this->characterId;
$logData->created->character->name = $this->characterName;
$logData->updated = (object) [];
$logData->updated->updated = strtotime($this->updated);
return $logData;
}
/**
* validate shipTypeId
* @param string $key
* @param string $val
* @return bool
*/
protected function validate_shipTypeId(string $key, string $val): bool {
return !empty((int)$val);
}
/**
* @return ConnectionModel
*/
public function getConnection() : ConnectionModel {
return $this->get('connectionId');
}
/**
* check object for model access
* @param CharacterModel $characterModel
* @return bool
*/
public function hasAccess(CharacterModel $characterModel) : bool {
$access = false;
if( !$this->dry() ){
$access = $this->getConnection()->hasAccess($characterModel);
}
return $access;
}
}

View File

@@ -137,9 +137,9 @@ class ConnectionModel extends AbstractMapTrackingModel {
/**
* check object for model access
* @param CharacterModel $characterModel
* @return mixed
* @return bool
*/
public function hasAccess(CharacterModel $characterModel){
public function hasAccess(CharacterModel $characterModel) : bool {
$access = false;
if( !$this->dry() ){
$access = $this->mapId->hasAccess($characterModel);
@@ -149,7 +149,7 @@ class ConnectionModel extends AbstractMapTrackingModel {
/**
* set default connection type by search route between endpoints
* @throws \Exception\PathfinderException
* @throws \Exception
*/
public function setDefaultTypeData(){
if(
@@ -215,7 +215,6 @@ class ConnectionModel extends AbstractMapTrackingModel {
* @param $pkeys
* @return bool
* @throws \Exception\DatabaseException
* @throws \Exception\PathfinderException
*/
public function beforeInsertEvent($self, $pkeys){
// check for "default" connection type and add them if missing
@@ -266,8 +265,8 @@ class ConnectionModel extends AbstractMapTrackingModel {
/**
* @param string $action
* @return Logging\LogInterface
* @throws \Exception\PathfinderException
* @return logging\LogInterface
* @throws \Exception\ConfigException
*/
public function newLog($action = ''): Logging\LogInterface{
return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData());
@@ -335,10 +334,6 @@ class ConnectionModel extends AbstractMapTrackingModel {
*/
public function getLogs(){
$logs = [];
$this->filter('connectionLog', [
'active = :active',
':active' => 1
]);
if($this->connectionLog){
$logs = $this->connectionLog;
@@ -377,20 +372,34 @@ class ConnectionModel extends AbstractMapTrackingModel {
return $logsData;
}
/**
* get blank connectionLog model
* @return ConnectionLogModel
* @throws \Exception
*/
public function getNewLog() : ConnectionLogModel {
/**
* @var $log ConnectionLogModel
*/
$log = self::getNew('ConnectionLogModel');
$log->connectionId = $this;
return $log;
}
/**
* log new mass for this connection
* @param CharacterLogModel $characterLog
* @return $this
* @return ConnectionModel
* @throws \Exception
*/
public function logMass(CharacterLogModel $characterLog){
public function logMass(CharacterLogModel $characterLog) : self {
if( !$characterLog->dry() ){
$log = $this->rel('connectionLog');
$log = $this->getNewLog();
$log->shipTypeId = $characterLog->shipTypeId;
$log->shipTypeName = $characterLog->shipTypeName;
$log->shipMass = $characterLog->shipMass;
$log->characterId = $characterLog->characterId->_id;
$log->characterName = $characterLog->characterId->name;
$log->connectionId = $this;
$log->save();
}

View File

@@ -181,7 +181,6 @@ class CorporationModel extends BasicModel {
* @param array $mapIds
* @param array $options
* @return array
* @throws \Exception\PathfinderException
*/
public function getMaps($mapIds = [], $options = []){
$maps = [];

View File

@@ -10,9 +10,9 @@ namespace Model;
use DB\SQL\Schema;
use data\file\FileHandler;
use Exception\ConfigException;
use lib\Config;
use lib\logging;
use Exception\PathfinderException;
class MapModel extends AbstractMapTrackingModel {
@@ -44,7 +44,7 @@ class MapModel extends AbstractMapTrackingModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry',
'validate' => 'notDry',
'activity-log' => true
],
'typeId' => [
@@ -57,7 +57,7 @@ class MapModel extends AbstractMapTrackingModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry',
'validate' => 'notDry',
'activity-log' => true
],
'name' => [
@@ -199,7 +199,6 @@ class MapModel extends AbstractMapTrackingModel {
* get data
* -> this includes system and connection data as well
* @return \stdClass
* @throws PathfinderException
* @throws \Exception
*/
public function getData(){
@@ -791,7 +790,6 @@ class MapModel extends AbstractMapTrackingModel {
* checks whether a character has access to this map or not
* @param CharacterModel $characterModel
* @return bool
* @throws PathfinderException
*/
public function hasAccess(CharacterModel $characterModel) : bool {
$hasAccess = false;
@@ -972,8 +970,8 @@ class MapModel extends AbstractMapTrackingModel {
/**
* @param string $action
* @return Logging\LogInterface
* @throws PathfinderException
* @return logging\LogInterface
* @throws ConfigException
*/
public function newLog($action = ''): Logging\LogInterface{
$logChannelData = $this->getLogChannelData();
@@ -1048,7 +1046,6 @@ class MapModel extends AbstractMapTrackingModel {
/**
* check if "activity logging" is enabled for this map type
* @return bool
* @throws PathfinderException
*/
public function isActivityLogEnabled(): bool {
return $this->logActivity && (bool) Config::getMapsDefaultConfig($this->typeId->name)['log_activity_enabled'];
@@ -1057,7 +1054,6 @@ class MapModel extends AbstractMapTrackingModel {
/**
* check if "history logging" is enabled for this map type
* @return bool
* @throws PathfinderException
*/
public function isHistoryLogEnabled(): bool {
return $this->logHistory && (bool) Config::getMapsDefaultConfig($this->typeId->name)['log_history_enabled'];
@@ -1067,7 +1063,7 @@ class MapModel extends AbstractMapTrackingModel {
* check if "Slack WebHook" is enabled for this map type
* @param string $channel
* @return bool
* @throws PathfinderException
* @throws ConfigException
*/
public function isSlackChannelEnabled(string $channel): bool {
$enabled = false;
@@ -1077,7 +1073,7 @@ class MapModel extends AbstractMapTrackingModel {
switch($channel){
case 'slackChannelHistory': $defaultMapConfigKey = 'send_history_slack_enabled'; break;
case 'slackChannelRally': $defaultMapConfigKey = 'send_rally_slack_enabled'; break;
default: throw new PathfinderException(sprintf(self::ERROR_SLACK_CHANNEL, $channel));
default: throw new ConfigException(sprintf(self::ERROR_SLACK_CHANNEL, $channel));
}
if((bool) Config::getMapsDefaultConfig($this->typeId->name)[$defaultMapConfigKey]){
@@ -1095,7 +1091,7 @@ class MapModel extends AbstractMapTrackingModel {
* check if "Discord WebHook" is enabled for this map type
* @param string $channel
* @return bool
* @throws PathfinderException
* @throws ConfigException
*/
public function isDiscordChannelEnabled(string $channel): bool {
$enabled = false;
@@ -1105,7 +1101,7 @@ class MapModel extends AbstractMapTrackingModel {
switch($channel){
case 'discordWebHookURLHistory': $defaultMapConfigKey = 'send_history_discord_enabled'; break;
case 'discordWebHookURLRally': $defaultMapConfigKey = 'send_rally_discord_enabled'; break;
default: throw new PathfinderException(sprintf(self::ERROR_DISCORD_CHANNEL, $channel));
default: throw new ConfigException(sprintf(self::ERROR_DISCORD_CHANNEL, $channel));
}
if((bool) Config::getMapsDefaultConfig($this->typeId->name)[$defaultMapConfigKey]){
@@ -1123,7 +1119,6 @@ class MapModel extends AbstractMapTrackingModel {
* check if "E-Mail" Log is enabled for this map
* @param string $type
* @return bool
* @throws PathfinderException
*/
public function isMailSendEnabled(string $type): bool{
$enabled = false;
@@ -1197,7 +1192,6 @@ class MapModel extends AbstractMapTrackingModel {
* @param string $type
* @param bool $addJson
* @return \stdClass
* @throws PathfinderException
*/
public function getSMTPConfig(string $type, bool $addJson = true): \stdClass{
$config = Config::getSMTPConfig();
@@ -1350,7 +1344,6 @@ class MapModel extends AbstractMapTrackingModel {
* get all active characters (with active log)
* grouped by systems
* @return \stdClass
* @throws PathfinderException
* @throws \Exception
*/
public function getUserData(){

View File

@@ -95,8 +95,6 @@ class SystemModel extends AbstractMapTrackingModel {
],
'description' => [
'type' => Schema::DT_TEXT,
'nullable' => false,
'default' => '',
'activity-log' => true,
'validate' => true
],
@@ -151,7 +149,7 @@ class SystemModel extends AbstractMapTrackingModel {
$systemData->locked = $this->locked;
$systemData->rallyUpdated = strtotime($this->rallyUpdated);
$systemData->rallyPoke = $this->rallyPoke;
$systemData->description = $this->description;
$systemData->description = $this->description ? : '';
$systemData->position = (object) [];
$systemData->position->x = $this->posX;
@@ -490,8 +488,8 @@ class SystemModel extends AbstractMapTrackingModel {
/**
* @param string $action
* @return Logging\LogInterface
* @throws \Exception\PathfinderException
* @return logging\LogInterface
* @throws \Exception\ConfigException
*/
public function newLog($action = ''): Logging\LogInterface{
return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData());
@@ -654,7 +652,7 @@ class SystemModel extends AbstractMapTrackingModel {
* -> send to an Email
* @param array $rallyData
* @param CharacterModel $characterModel
* @throws \Exception\PathfinderException
* @throws \Exception\ConfigException
*/
public function sendRallyPoke(array $rallyData, CharacterModel $characterModel){
// rally log needs at least one handler to be valid

View File

@@ -162,8 +162,8 @@ class SystemSignatureModel extends AbstractMapTrackingModel {
/**
* @param string $action
* @return Logging\LogInterface
* @throws \Exception\PathfinderException
* @return logging\LogInterface
* @throws \Exception\ConfigException
*/
public function newLog($action = ''): Logging\LogInterface{
return $this->getMap()->newLog($action)->setTempData($this->getLogObjectData());

View File

@@ -33,14 +33,15 @@ class CategoryModel extends BasicUniverseModel {
/**
* get category data
* @return object
* @param array $additionalData
* @return null|object
*/
public function getData(){
public function getData(array $additionalData = []){
$categoryData = (object) [];
$categoryData->id = $this->id;
$categoryData->name = $this->name;
if($groupsData = $this->getGroupsData()){
if($groupsData = $this->getGroupsData($additionalData)){
$categoryData->groups = $groupsData;
}
@@ -69,14 +70,15 @@ class CategoryModel extends BasicUniverseModel {
}
/**
* @param array $additionalData
* @return array
*/
protected function getGroupsData() : array {
protected function getGroupsData(array $additionalData = []) : array {
$groupsData = [];
$groups = $this->getGroups();
foreach($groups as $group){
$groupsData[] = $group->getData();
$groupsData[] = $group->getData($additionalData);
}
return $groupsData;

View File

@@ -30,7 +30,7 @@ class ConstellationModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'x' => [
'type' => Schema::DT_BIGINT,

View File

@@ -36,7 +36,7 @@ class GroupModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'types' => [
'has-many' => ['Model\Universe\TypeModel', 'groupId']
@@ -45,14 +45,15 @@ class GroupModel extends BasicUniverseModel {
/**
* get group data
* @return object
* @param array $additionalData
* @return null|object
*/
public function getData(){
public function getData(array $additionalData = []){
$groupData = (object) [];
$groupData->id = $this->id;
$groupData->name = $this->name;
if($typesData = $this->getTypesData()){
if($typesData = $this->getTypesData($additionalData)){
$groupData->types = $typesData;
}
@@ -81,14 +82,15 @@ class GroupModel extends BasicUniverseModel {
}
/**
* @param array $additionalData
* @return array
*/
protected function getTypesData() : array {
protected function getTypesData(array $additionalData = []) : array {
$typesData = [];
$types = $this->getTypes();
foreach($types as $type){
$typesData[] = $type->getData();
$typesData[] = $type->getData($additionalData);
}
return $typesData;

View File

@@ -30,7 +30,7 @@ class PlanetModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'typeId' => [
'type' => Schema::DT_INT,
@@ -42,7 +42,7 @@ class PlanetModel extends BasicUniverseModel {
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'x' => [
'type' => Schema::DT_BIGINT,

View File

@@ -30,7 +30,7 @@ class StargateModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'typeId' => [
'type' => Schema::DT_INT,
@@ -42,7 +42,7 @@ class StargateModel extends BasicUniverseModel {
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'destinationSystemId' => [
'type' => Schema::DT_INT,
@@ -54,7 +54,7 @@ class StargateModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'x' => [
'type' => Schema::DT_BIGINT,

View File

@@ -30,7 +30,7 @@ class StarModel extends BasicUniverseModel {
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'age' => [
'type' => Schema::DT_BIGINT,

View File

@@ -37,7 +37,7 @@ class StructureModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'x' => [
'type' => Schema::DT_FLOAT,

View File

@@ -32,7 +32,7 @@ class SystemModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'starId' => [
'type' => Schema::DT_INT,
@@ -44,7 +44,7 @@ class SystemModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'security' => [
'type' => Schema::DT_VARCHAR128

View File

@@ -25,7 +25,7 @@ class SystemStaticModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'wormholeId' => [
'type' => Schema::DT_INT,
@@ -37,7 +37,7 @@ class SystemStaticModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
]
];

View File

@@ -59,7 +59,7 @@ class TypeModel extends BasicUniverseModel {
'on-delete' => 'CASCADE'
]
],
'validate' => 'validate_notDry',
'validate' => 'notDry',
],
'marketGroupId' => [
'type' => Schema::DT_INT,
@@ -99,13 +99,18 @@ class TypeModel extends BasicUniverseModel {
/**
* get type data
* @return object
* @param array $additionalData
* @return null|object
*/
public function getData(){
public function getData(array $additionalData = []){
$typeData = (object) [];
$typeData->id = $this->id;
$typeData->name = $this->name;
foreach($additionalData as $key){
$typeData->$key = $this->$key;
}
return $typeData;
}

View File

@@ -35,7 +35,7 @@ class WormholeModel extends BasicUniverseModel {
'on-delete' => 'SET NULL'
]
],
'validate' => 'validate_notDry'
'validate' => 'notDry'
],
'static' => [
'type' => Schema::DT_BOOL,

View File

@@ -94,7 +94,6 @@ class UserModel extends BasicModel {
* @param UserModel $self
* @param $pkeys
* @return bool
* @throws Exception\PathfinderException
* @throws Exception\RegistrationException
*/
public function beforeInsertEvent($self, $pkeys){
@@ -137,7 +136,6 @@ class UserModel extends BasicModel {
/**
* checks whether user has a valid email address and pathfinder has a valid SMTP config
* @return bool
* @throws Exception\PathfinderException
*/
protected function isMailSendEnabled() : bool{
return Config::isValidSMTPConfig($this->getSMTPConfig());
@@ -146,7 +144,6 @@ class UserModel extends BasicModel {
/**
* get SMTP config for this user
* @return \stdClass
* @throws Exception\PathfinderException
*/
protected function getSMTPConfig() : \stdClass{
$config = Config::getSMTPConfig();

View File

@@ -18,3 +18,6 @@ GET|POST /api/@controller/@action [ajax] = Controller\Api\@cont
GET|POST /api/@controller/@action/@arg1 [ajax] = Controller\Api\@controller->@action, 0, 512
GET|POST /api/@controller/@action/@arg1/@arg2 [ajax] = Controller\Api\@controller->@action, 0, 512
[maps]
/api/rest/@controller* [ajax] = Controller\Api\Rest\@controller, 0, 512
/api/rest/@controller/@id [ajax] = Controller\Api\Rest\@controller, 0, 512

View File

@@ -21,6 +21,8 @@
}],
"require": {
"php-64bit": ">=7.0",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",

View File

@@ -21,6 +21,8 @@
}],
"require": {
"php-64bit": ">=7.0",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",

View File

@@ -54,7 +54,7 @@ requirejs.config({
blueImpGalleryBootstrap: 'lib/bootstrap-image-gallery', // v3.4.2 Bootstrap extension for Blue Imp Gallery - https://blueimp.github.io/Bootstrap-Image-Gallery
bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.5 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
lazyload: 'lib/jquery.lazyload.min', // v1.9.5 LazyLoader images - http://www.appelsiini.net/projects/lazyload
lazyload: 'lib/jquery.lazyload.min', // v1.9.7 LazyLoader images - http://www.appelsiini.net/projects/lazyload
sortable: 'lib/sortable.min', // v1.6.0 Sortable - drag&drop reorder - https://github.com/rubaxa/Sortable
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org

View File

@@ -18,6 +18,9 @@ define([
lengthMenu: [[5, 10, 25, 50, -1], [5, 10, 25, 50, 'All']],
order: [], // no default order because columnDefs is empty
autoWidth: false,
language: {
info: '_START_ - _END_ of _TOTAL_ entries'
},
responsive: {
breakpoints: Init.breakpoints,
details: false

View File

@@ -9,6 +9,7 @@ define(['jquery'], ($) => {
let Config = {
path: {
img: '/public/img/', // path for images
api: '/api/rest', //ajax URL - REST API
// user API
getCaptcha: '/api/user/getCaptcha', // ajax URL - get captcha image
getServerStatus: '/api/user/getEveServerStatus', // ajax URL - get EVE-Online server status

View File

@@ -343,7 +343,7 @@ define([
let initGallery = (newElements) => {
if( newElements.length > 0){
// We have to add ALL thumbnail elements to the gallery!
// -> even those wthat are invisible (not lazyLoaded) now!
// -> even those which are invisible (not lazyLoaded) now!
// -> This is required for "swipe" through all images
let allThumbLinks = getThumbnailElements();
@@ -814,7 +814,7 @@ define([
// init carousel
initCarousel();
// init scrollspy
// init scrollSpy
// -> after "Carousel"! required for correct "viewport" calculation (Gallery)!
initScrollSpy();

View File

@@ -79,15 +79,21 @@ define([
let reason = status + ' ' + jqXHR.status + ': ' + error;
let errorData = [];
let redirect = false; // redirect user to other page e.g. login
let reload = true; // reload current page (default: true)
if(jqXHR.responseJSON){
// handle JSON
let errorObj = jqXHR.responseJSON;
let responseObj = jqXHR.responseJSON;
if(
errorObj.error &&
errorObj.error.length > 0
responseObj.error &&
responseObj.error.length > 0
){
errorData = errorObj.error;
errorData = responseObj.error;
}
if(responseObj.reroute){
redirect = responseObj.reroute;
}
}else{
// handle HTML
@@ -98,7 +104,13 @@ define([
}
console.error(' ↪ %s Error response: %o', jqXHR.url, errorData);
$(document).trigger('pf:shutdown', {status: jqXHR.status, reason: reason, error: errorData});
$(document).trigger('pf:shutdown', {
status: jqXHR.status,
reason: reason,
error: errorData,
redirect: redirect,
reload: reload
});
};
// map init functions =========================================================================================

View File

@@ -787,12 +787,14 @@ define([
label: '<i class="fas fa-fw fa-sync"></i> restart',
className: ['btn-primary'].join(' '),
callback: function(){
// check if error was 5xx -> reload page
// -> else try to logout -> ajax request
if(data.status >= 500 && data.status < 600){
// redirect to login
window.location = '../';
if(data.redirect) {
// ... redirect user to e.g. login form page ...
Util.redirect(data.redirect, ['logout']);
}else if(data.reload){
// ... or reload current page ...
location.reload();
}else{
// ... fallback try to logout user
documentElement.trigger('pf:menuLogout');
}
}
@@ -811,12 +813,9 @@ define([
};
// add error information (if available)
if(
data.error &&
data.error.length
){
for(let i = 0; i < data.error.length; i++){
options.content.textSmaller.push(data.error[i].message);
if(data.error && data.error.length){
for(let error of data.error){
options.content.textSmaller.push(error.message);
}
}

View File

@@ -76,7 +76,7 @@ define([
dialogElement.find('.modal-content').hideLoadingAnimation();
if(responseData.reroute !== undefined){
Util.redirect(responseData.reroute, []);
Util.redirect(responseData.reroute);
}else if(
responseData.error &&
responseData.error.length > 0

View File

@@ -753,7 +753,7 @@ define([
* show activity stats dialog
*/
$.fn.showStatsDialog = function(){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'datatables.loader'], function(template, Mustache){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'datatables.loader'], (template, Mustache) => {
// get current statistics map settings
let logActivityEnabled = false;
let activeMap = Util.getMapModule().getActiveMap();

View File

@@ -12,7 +12,8 @@ define([
let config = {
// Select2
resultOptionImageClass: 'pf-result-image' // class for Select2 result option entry with image
resultOptionImageClass: 'pf-result-image', // class for Select2 result option entry with image
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad' // class for Select2 result images that should be lazy loaded
};
/**
@@ -60,7 +61,7 @@ define([
}
if(imagePath){
thumb = '<img src="' + imagePath + '" style="max-width: 100%" />';
thumb = '<img class="' + config.select2ImageLazyLoadClass + '" data-original="' + imagePath + '" style="max-width: 100%"/>';
}else if(iconName){
thumb = '<i class="fas fa-fw ' + iconName + '" ></i>';
}
@@ -564,6 +565,7 @@ define([
return group;
});
},*/
disabled: options.hasOwnProperty('disabled') ? options.disabled : false,
allowClear: options.maxSelectionLength <= 1,
maximumSelectionLength: options.maxSelectionLength,
templateResult: formatCategoryTypeResultData
@@ -605,6 +607,7 @@ define([
return {
id: type.id,
text: type.name,
mass: type.hasOwnProperty('mass') ? type.mass : null,
groupId: this.groupId,
categoryId: this.categoryId,
categoryType: this.categoryType

View File

@@ -6,8 +6,9 @@ define([
'jquery',
'app/init',
'app/util',
'bootbox',
'app/map/util'
], ($, Init, Util, MapUtil) => {
], ($, Init, Util, bootbox, MapUtil) => {
'use strict';
let config = {
@@ -46,9 +47,17 @@ define([
connectionInfoTableCellMassLeftClass: 'pf-connection-info-mass-left', // class for "mass left" table cell
// dataTable
tableToolbarCondensedClass: 'pf-dataTable-condensed-toolbar', // class for condensed table toolbar
connectionInfoTableClass: 'pf-connection-info-table', // class for connection tables
tableCellImageClass: 'pf-table-image-cell', // class for table "image" cells
tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells
tableCellActionClass: 'pf-table-action-cell', // class for "action" cells
// connection dialog
connectionDialogId: 'pf-connection-info-dialog', // id for "connection" dialog
typeSelectId: 'pf-connection-info-dialog-type-select', // id for "ship type" select
shipMassId: 'pf-connection-info-dialog-mass', // id for "ship mass" input
characterSelectId: 'pf-connection-info-dialog-character-select', // id for "character" select
// config
showShip: true // default for "show current ship mass" toggle
@@ -511,6 +520,34 @@ define([
return moduleElement.find('.' + config.connectionInfoPanelClass).not('#' + getConnectionElementId(0));
};
/**
* enrich connectionData with "logs" data (if available) and other "missing" data
* @param connectionsData
* @param newConnectionsData
* @returns {*}
*/
let enrichConnectionsData = (connectionsData, newConnectionsData) => {
for(let i = 0; i < connectionsData.length; i++){
for(let newConnectionData of newConnectionsData){
if(connectionsData[i].id === newConnectionData.id){
// copy some missing data
connectionsData[i].character = newConnectionData.character;
connectionsData[i].created = newConnectionData.created;
// check for mass logs and copy data
if(newConnectionData.logs && newConnectionData.logs.length){
connectionsData[i].logs = newConnectionData.logs;
}
// check for signatures and copy data
if(newConnectionData.signatures && newConnectionData.signatures.length){
connectionsData[i].signatures = newConnectionData.signatures;
}
break;
}
}
}
return connectionsData;
};
/**
* request connection log data
* @param requestData
@@ -530,24 +567,7 @@ define([
dataType: 'json',
context: context
}).done(function(connectionsData){
// enrich connectionData with "logs" data (if available) and other "missing" data
for(let i = 0; i < this.connectionsData.length; i++){
for(let connectionData of connectionsData){
if(this.connectionsData[i].id === connectionData.id){
// copy some missing data
this.connectionsData[i].created = connectionData.created;
// check for mass logs and copy data
if(connectionData.logs && connectionData.logs.length){
this.connectionsData[i].logs = connectionData.logs;
}
// check for signatures and copy data
if(connectionData.signatures && connectionData.signatures.length){
this.connectionsData[i].signatures = connectionData.signatures;
}
break;
}
}
}
this.connectionsData = enrichConnectionsData(this.connectionsData, connectionsData);
callback(this.moduleElement, this.connectionsData);
}).always(function(){
@@ -592,9 +612,9 @@ define([
*/
let addConnectionsData = (moduleElement, connectionsData) => {
let getRowIndexesByData = (dataTable, colName, value) => {
return dataTable.rows().eq(0).filter((rowIdx) => {
return (dataTable.cell(rowIdx, colName + ':name').data() === value);
let getRowIndexesByData = (tableApi, colName, value) => {
return tableApi.rows().eq(0).filter((rowIdx) => {
return (tableApi.cell(rowIdx, colName + ':name').data() === value);
});
};
@@ -607,42 +627,47 @@ define([
connectionInfoElement.data('connectionData', connectionData);
// update dataTable ---------------------------------------------------------------
let dataTable = connectionElement.find('.dataTable').dataTable().api();
let tableApi = connectionElement.find('.dataTable').dataTable().api();
if(connectionData.logs && connectionData.logs.length > 0){
for(let i = 0; i < connectionData.logs.length; i++){
let rowData = connectionData.logs[i];
let row = null;
let rowNew = null;
let animationStatus = null;
let indexes = getRowIndexesByData(dataTable, 'index', rowData.id);
let indexes = getRowIndexesByData(tableApi, 'index', rowData.id);
if(indexes.length === 0){
// row not found -> add new row
row = dataTable.row.add( rowData );
rowNew = tableApi.row.add(rowData);
animationStatus = 'added';
}
/* else{
// we DON´t expect changes -> no row update)
}else{
// update row with FIRST index
//row = dataTable.row( parseInt(indexes[0]) );
// update row data
//row.data(connectionData.logs[i]);
//animationStatus = 'changed';
} */
let row = tableApi.row( parseInt(indexes[0]));
let rowDataCurrent = row.data();
// check if row data changed
if(rowDataCurrent.updated.updated !== rowData.updated.updated){
// ... row changed -> delete old and re-add
// -> cell actions might have changed
row.remove();
rowNew = tableApi.row.add(rowData);
animationStatus = 'changed';
}
}
if(
animationStatus !== null &&
row.length > 0
rowNew.length > 0
){
row.nodes().to$().data('animationStatus', animationStatus);
rowNew.nodes().to$().data('animationStatus', animationStatus);
}
}
}else{
// clear table or leave empty
dataTable.clear();
tableApi.clear();
}
// redraw dataTable
dataTable.draw(false);
tableApi.draw(false);
}
}
};
@@ -663,11 +688,45 @@ define([
let table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', 'nowrap', config.connectionInfoTableClass].join(' ')
}).append('<tfoot><tr><th></th><th></th><th></th><th></th><th></th></tr></tfoot>');
}).append('<tfoot><tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></tfoot>');
connectionElement.append(table);
// init empty table
let logTable = table.DataTable({
dom: '<"container-fluid"' +
'<"row ' + config.tableToolbarCondensedClass + '"' +
'<"col-xs-5"i><"col-xs-5"p><"col-xs-2 text-right"B>>' +
'<"row"tr>>',
buttons: {
name: 'tableTools',
buttons: [
{
name: 'addLog',
className: config.moduleHeadlineIconClass,
text: '<i class="fa fa-plus"></i>',
action: function(e, tableApi, node, conf){
let logData = {};
// pre-fill form with current character data (if available)
let currentUserData = Util.getCurrentUserData();
if(currentUserData && currentUserData.character){
logData.character = {
id: currentUserData.character.id,
name: currentUserData.character.name
};
if(currentUserData.character.log){
logData.ship = {
id: currentUserData.character.log.ship.typeId,
name: currentUserData.character.log.ship.typeName
};
}
}
showLogDialog(moduleElement, connectionElement, connectionData, logData);
}
}
]
},
pageLength: 8,
paging: true,
pagingType: 'simple',
@@ -678,11 +737,14 @@ define([
searching: false,
hover: false,
autoWidth: false,
// rowId: 'systemTo',
language: {
emptyTable: 'No jumps recorded',
info: '_START_ to _END_ of _MAX_',
infoEmpty: ''
info: '_START_ - _END_ of _MAX_',
infoEmpty: '',
paginate: {
previous: '',
next: ''
}
},
columnDefs: [
{
@@ -692,10 +754,25 @@ define([
orderable: false,
searchable: false,
width: 20,
class: 'text-center',
data: 'id'
className: ['text-center', 'txt-color'].join(' '),
data: 'id',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
if(
!rowData.record ||
(rowData.updated.updated !== rowData.created.created)
){
// log was manually modified or added
$(cell)
.addClass(Util.config.helpClass)
.addClass( 'txt-color-orange').tooltip({
container: 'body',
title: 'added/updated manually'
});
}
}
},{
targets: 1,
name: 'ship',
title: '',
width: 26,
orderable: false,
@@ -715,16 +792,17 @@ define([
}
},{
targets: 2,
name: 'character',
title: '',
width: 26,
orderable: false,
className: [Util.config.helpDefaultClass, 'text-center', config.tableCellImageClass].join(' '),
data: 'created.character',
data: 'character',
render: {
_: function(data, type, row){
let value = data.name;
_: (cellData, type, rowData, meta) => {
let value = cellData.name;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg" title="' + value + '" data-toggle="tooltip" />';
value = '<img src="' + Init.url.ccpImageServer + '/Character/' + cellData.id + '_32.jpg" title="' + value + '" data-toggle="tooltip" />';
}
return value;
}
@@ -734,20 +812,26 @@ define([
}
},{
targets: 3,
name: 'mass',
title: 'mass',
className: ['text-right'].join(' ') ,
data: 'ship.mass',
render: {
_: function(data, type, row){
let value = data;
_: (cellData, type, rowData, meta) => {
let value = cellData;
if(type === 'display'){
value = Util.formatMassValue(value);
if(!rowData.active){
// log is "deleted"
value = '<span class="pf-font-line-through txt-color txt-color-red">' + value + '</span>';
}
}
return value;
}
}
},{
targets: 4,
name: 'created',
title: 'log',
width: 55,
className: ['text-right', config.tableCellCounterClass].join(' '),
@@ -755,6 +839,108 @@ define([
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter('d');
}
},{
targets: 5,
name: 'edit',
title: '',
orderable: false,
searchable: false,
width: 10,
className: ['text-center', config.tableCellActionClass, config.moduleHeadlineIconClass].join(' '),
data: null,
render: {
display: data => {
let icon = '';
if(data.active){
icon = '<i class="fas fa-pen"></i>';
}
return icon;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if($(cell).is(':empty')){
$(cell).removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
}else{
$(cell).on('click', function(e){
showLogDialog(moduleElement, connectionElement, connectionData, rowData);
});
}
}
},{
targets: 6,
name: 'delete',
title: '',
orderable: false,
searchable: false,
width: 10,
className: ['text-center', config.tableCellActionClass].join(' '),
data: 'active',
render: {
display: data => {
let val = '<i class="fas fa-plus"></i>';
if(data){
val = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
}
return val;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if(rowData.active){
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete jump log',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
onConfirm : function(e, target){
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tableApi.row($(cell).parents('tr')).data();
connectionElement.find('table').showLoadingAnimation();
request('DELETE', 'log', rowData.id, {}, {
connectionElement: connectionElement
}, requestAlways)
.then(
payload => {
addConnectionsData(moduleElement, enrichConnectionsData([connectionData], payload.data));
},
Util.handleAjaxErrorResponse
);
}
};
// init confirmation dialog
$(cell).confirmation(confirmationSettings);
}else {
$(cell).on('click', function(e){
connectionElement.find('table').showLoadingAnimation();
let requestData = {
active: 1
};
request('PATCH', 'log', rowData.id, requestData, {
connectionElement: connectionElement
}, requestAlways)
.then(
payload => {
addConnectionsData(moduleElement, enrichConnectionsData([connectionData], payload.data));
},
Util.handleAjaxErrorResponse
);
});
}
}
}
],
drawCallback: function(settings){
@@ -772,27 +958,34 @@ define([
},
footerCallback: function(row, data, start, end, display ){
let api = this.api();
let sumColumnIndexes = [3];
let tableApi = this.api();
let sumColumnIndexes = ['mass:name', 'delete:name'];
// column data for "sum" columns over this page
let pageTotalColumns = api
let pageTotalColumns = tableApi
.columns( sumColumnIndexes, { page: 'all'} )
.data();
// sum columns for "total" sum
pageTotalColumns.each((colData, index) => {
pageTotalColumns[index] = colData.reduce((a, b) => {
return parseInt(a) + parseInt(b);
pageTotalColumns.each((colData, colIndex) => {
pageTotalColumns[colIndex] = colData.reduce((sum, val, rowIndex) => {
// sum "mass" (colIndex 0) only if not "deleted" (colIndex 1)
if(colIndex === 0 && pageTotalColumns[1][rowIndex]){
return sum + parseInt(val);
}else{
return sum;
}
}, 0);
});
$(sumColumnIndexes).each((index, value) => {
$( api.column( value ).footer() ).text( Util.formatMassValue(pageTotalColumns[index]) );
sumColumnIndexes.forEach((colSelector, index) => {
// only "mass" column footer needs updates
if(colSelector === 'mass:name'){
$(tableApi.column(colSelector).footer()).text( Util.formatMassValue(pageTotalColumns[index]) );
// save mass for further reCalculation of "info" table
connectionElement.find('.' + config.connectionInfoTableCellMassLogClass).data('mass', pageTotalColumns[index]);
// save mass for further reCalculation of "info" table
connectionElement.find('.' + config.connectionInfoTableCellMassLogClass).data('mass', pageTotalColumns[index]);
}
});
// calculate "info" table -----------------------------------------------------
@@ -805,14 +998,186 @@ define([
logTable.on('order.dt search.dt', function(){
let pageInfo = logTable.page.info();
logTable.column(0, {search:'applied', order:'applied'}).nodes().each((cell, i) => {
let content = (pageInfo.recordsTotal - i) + '.&nbsp;&nbsp;';
logTable.column('index:name', {search:'applied', order:'applied'}).nodes().each((cell, i) => {
let content = (pageInfo.recordsTotal - i) + '.';
$(cell).html(content);
});
});
}
};
let request = (action, entity, ids = [], data = {}, context = {}, always = null) => {
let requestExecutor = (resolve, reject) => {
let payload = {
action: 'request',
name: action.toLowerCase() + entity.charAt(0).toUpperCase() + entity.slice(1)
};
// build request url --------------------------------------------------------------------------------------
let url = Init.path.api + '/' + entity;
let path = '';
if(isNaN(ids)){
if(Array.isArray(ids)){
path += '/' + ids.join(',');
}
}else{
let id = parseInt(ids, 10);
path += id ? '/' + id : '';
}
url += path;
$.ajax({
type: action,
url: url,
data: JSON.stringify(data),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
context: context
}).done(function(response){
payload.data = response;
payload.context = this;
resolve(payload);
}).fail(function(jqXHR, status, error){
payload.data = {
jqXHR: jqXHR,
status: status,
error: error
};
payload.context = this;
reject(payload);
}).always(function(){
if(always){
always(this);
}
});
};
return new Promise(requestExecutor);
};
/**
*
* @param context
*/
let requestAlways = (context) => {
context.connectionElement.find('table').hideLoadingAnimation();
};
/**
* show jump log dialog
* @param moduleElement
* @param connectionElement
* @param connectionData
* @param logData
*/
let showLogDialog = (moduleElement, connectionElement, connectionData, logData = {}) => {
let data = {
id: config.connectionDialogId,
typeSelectId: config.typeSelectId,
shipMassId: config.shipMassId,
characterSelectId: config.characterSelectId,
logData: logData,
massFormat: () => {
return (val, render) => {
return (parseInt(render(val) || 0) / 1000) || '';
};
}
};
requirejs(['text!templates/dialog/connection_log.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
let connectionDialog = bootbox.dialog({
title: 'Jump log',
message: content,
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fas fa-fw fa-check"></i>&nbsp;save',
className: 'btn-success',
callback: function(){
let form = this.find('form');
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
if(formValid){
// get form data
let formData = form.getFormValues();
formData.id = Util.getObjVal(logData, 'id') || 0;
formData.connectionId = Util.getObjVal(connectionData, 'id') || 0;
formData.shipTypeId = Util.getObjVal(formData, 'shipTypeId') || 0;
formData.shipMass = parseInt((Util.getObjVal(formData, 'shipMass') || 0) * 1000);
formData.characterId = Util.getObjVal(formData, 'characterId') || 0;
// we need some "additional" form data from the Select2 dropdown
// -> data is required on the backend side
let formDataShip = form.find('#' + config.typeSelectId).select2('data');
let formDataCharacter = form.find('#' + config.characterSelectId).select2('data');
formData.shipTypeName = formDataShip.length ? formDataShip[0].text : '';
formData.characterName = formDataCharacter.length ? formDataCharacter[0].text : '';
let method = formData.id ? 'PATCH' : 'PUT';
request(method, 'log', formData.id, formData, {
connectionElement: connectionElement,
formElement: form
}, requestAlways)
.then(
payload => {
addConnectionsData(moduleElement, enrichConnectionsData([connectionData], payload.data));
this.modal('hide');
},
Util.handleAjaxErrorResponse
);
}
return false;
}
}
}
});
connectionDialog.on('show.bs.modal', function(e){
let modalContent = $('#' + config.connectionDialogId);
// init type select live search
let selectElementType = modalContent.find('#' + config.typeSelectId);
selectElementType.initUniverseTypeSelect({
categoryIds: [6],
maxSelectionLength: 1,
selected: [Util.getObjVal(logData, 'ship.typeId')]
}).on('select2:select select2:unselecting', function(e){
// get ship mass from selected ship type and update mass input field
let shipMass = e.params.data ? e.params.data.mass / 1000 : '';
modalContent.find('#' + config.shipMassId).val(shipMass);
});
// init character select live search
let selectElementCharacter = modalContent.find('#' + config.characterSelectId);
selectElementCharacter.initUniverseSearch({
categoryNames: ['character'],
maxSelectionLength: 1
});
});
// show dialog
connectionDialog.modal('show');
});
};
/**
* remove connection Panel from moduleElement
* @param connectionElement

View File

@@ -337,7 +337,7 @@ define([
data: statusData
});
// init character counter
// init char counter
let textarea = modalContent.find('#' + config.descriptionTextareaId);
let charCounter = modalContent.find('.' + config.descriptionTextareaCharCounter);
Util.updateCounter(textarea, charCounter, maxDescriptionLength);

View File

@@ -8,6 +8,7 @@ define([
'conf/signature_type',
'bootbox',
'localForage',
'lazyload',
'velocity',
'velocityUI',
'customScrollbar',
@@ -70,6 +71,7 @@ define([
// select2
select2Class: 'pf-select2', // class for all "Select2" <select> elements
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad',
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
@@ -1059,6 +1061,9 @@ define([
// in order to make mCustomScrollbar mouseWheel enable works correctly
$(resultsWrapper).find('ul.select2-results__options').off('mousewheel');
// preload images that are not visible yet
let lazyLoadImagesOffset = 240;
resultsWrapper.mCustomScrollbar({
mouseWheel: {
enable: true,
@@ -1085,10 +1090,35 @@ define([
// -> this is because the initPassiveEvents() delegates the mouseWheel events
togglePageScroll(false);
},
onUpdate: function(a){
// whenever the scroll content updates -> init lazyLoad for potential images
$('.' + config.select2ImageLazyLoadClass).lazyload({
container: this,
threshold: lazyLoadImagesOffset,
event: 'pf:lazyLoad'
});
},
onTotalScroll: function(){
// we want to "trigger" Select2´s 'scroll' event
// in order to make its "infinite scrolling" function working
this.mcs.content.find(':first-child').trigger('scroll');
},
whileScrolling: function(){
// lazy load for images -> reduce number of calculations by % 10
if(0 === this.mcs.top % 10){
let scroller = $(this).find('.mCSB_container');
let scrollerBox = scroller.closest('.mCustomScrollBox');
scrollerBox.find('.' + config.select2ImageLazyLoadClass).filter(function(){
let $this = $(this);
if($this.attr('src') === $this.attr('data-original')) return false;
let scrollerTop = scroller.position().top;
let scrollerHeight = scrollerBox.height();
let offset = $this.closest('div').position();
return (offset.top - lazyLoadImagesOffset < scrollerHeight - scrollerTop);
}).trigger('pf:lazyLoad');
}
}
}
});
@@ -1108,7 +1138,7 @@ define([
return wrapper;
};
// global open event
// global opened event
$(document).on('select2:open', '.' + config.select2Class, function(e){
let resultsWrapper = getResultsWrapper(this);
if(resultsWrapper){
@@ -1497,6 +1527,37 @@ define([
});
};
/**
* global ajax error handler -> handles .fail() requests
* @param payload
*/
let handleAjaxErrorResponse = (payload) => {
// handle only request errors
if(payload.action === 'request'){
let jqXHR = payload.data.jqXHR;
let reason = '';
if(jqXHR.responseJSON){
// ... valid JSON response
let response = jqXHR.responseJSON;
if(response.error && response.error.length > 0){
// build error notification reason from errors
reason = response.error.map(error => error.status).join('\n');
// check if errors might belong to a HTML form -> check "context"
if(payload.context.formElement){
// show form messages e.g. validation errors
payload.context.formElement.showFormMessage(response.error);
}
}
}else{
reason = 'Invalid JSON response';
}
showNotify({title: jqXHR.status + ': ' + payload.name, text: reason, type: 'error'});
}
};
/**
* get WebSocket readyState description from ID
* https://developer.mozilla.org/de/docs/Web/API/WebSocket
@@ -2864,7 +2925,7 @@ define([
* @param url
* @param params
*/
let redirect = (url, params) => {
let redirect = (url, params = []) => {
let currentUrl = document.URL;
if(url !== currentUrl){
@@ -2976,6 +3037,7 @@ define([
stopTabBlink: stopTabBlink,
getLogInfo: getLogInfo,
ajaxSetup: ajaxSetup,
handleAjaxErrorResponse: handleAjaxErrorResponse,
setSyncStatus: setSyncStatus,
getSyncType: getSyncType,
isXHRAborted: isXHRAborted,

View File

@@ -1,2 +1,2 @@
/*! Lazy Load 1.9.5 - MIT license - Copyright 2010-2015 Mika Tuupola */
/*! Lazy Load 1.9.7 - MIT license - Copyright 2010-2015 Mika Tuupola */
!function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!1,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("<img />").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document);

View File

@@ -6,36 +6,35 @@
},
"description": "Pathfinder is a system mapping tool for EVE ONLINE",
"main": "index.php",
"dependencies": {
},
"dependencies": {},
"devDependencies": {
"ansi-colors": "^3.1.0",
"fancy-log": "1.3.x",
"file-extension": "^4.0.5",
"flat": "^4.1.0",
"gulp": "next",
"gulp-requirejs-optimize": "1.3.x",
"gulp-brotli": "1.2.x",
"gulp-bytediff": "1.0.x",
"gulp-clean-css": "^3.10.0",
"gulp-compass": "2.1.x",
"gulp-debug": "^4.0.0",
"gulp-filter": "5.1.x",
"gulp-gzip": "1.x.x",
"gulp-if": "2.0.x",
"gulp-jshint": "2.1.x",
"gulp-rename": "^1.4.0",
"gulp-requirejs-optimize": "1.3.x",
"gulp-sourcemaps": "2.6.x",
"gulp-gzip": "1.x.x",
"gulp-brotli": "1.2.x",
"gulp-uglify": "^3.0.x",
"gulp-rename": "1.2.x",
"gulp-compass": "2.1.x",
"gulp-clean-css": "3.x.x",
"gulp-bytediff": "1.0.x",
"gulp-debug": "^3.1.x",
"node-notifier": "5.2.x",
"uglify-es": "^3.0.x",
"jshint": "^2.9.x",
"gulp-uglify": "^3.0.1",
"jshint": "^2.9.6",
"jshint-stylish": "^2.x.x",
"terminal-table": "0.0.x",
"pretty-bytes": "4.0.x",
"promised-del": "1.0.x",
"flat": "2.0.x",
"lodash.padend": "4.6.x",
"node-notifier": "5.2.x",
"pretty-bytes": "^5.1.0",
"promised-del": "1.0.x",
"slash": "2.x.x",
"file-extension": "3.1.x",
"fancy-log": "1.3.x",
"ansi-colors": "1.x.x"
"terminal-table": "0.0.x",
"uglify-es": "^3.0.x"
},
"scripts": {
"gulp": "gulp"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -54,7 +54,7 @@ requirejs.config({
blueImpGalleryBootstrap: 'lib/bootstrap-image-gallery', // v3.4.2 Bootstrap extension for Blue Imp Gallery - https://blueimp.github.io/Bootstrap-Image-Gallery
bootstrapConfirmation: 'lib/bootstrap-confirmation', // v1.0.5 Bootstrap extension for inline confirm dialog - https://github.com/tavicu/bs-confirmation
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
lazyload: 'lib/jquery.lazyload.min', // v1.9.5 LazyLoader images - http://www.appelsiini.net/projects/lazyload
lazyload: 'lib/jquery.lazyload.min', // v1.9.7 LazyLoader images - http://www.appelsiini.net/projects/lazyload
sortable: 'lib/sortable.min', // v1.6.0 Sortable - drag&drop reorder - https://github.com/rubaxa/Sortable
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org

View File

@@ -18,6 +18,9 @@ define([
lengthMenu: [[5, 10, 25, 50, -1], [5, 10, 25, 50, 'All']],
order: [], // no default order because columnDefs is empty
autoWidth: false,
language: {
info: '_START_ - _END_ of _TOTAL_ entries'
},
responsive: {
breakpoints: Init.breakpoints,
details: false

View File

@@ -9,6 +9,7 @@ define(['jquery'], ($) => {
let Config = {
path: {
img: '/public/img/', // path for images
api: '/api/rest', //ajax URL - REST API
// user API
getCaptcha: '/api/user/getCaptcha', // ajax URL - get captcha image
getServerStatus: '/api/user/getEveServerStatus', // ajax URL - get EVE-Online server status

View File

@@ -343,7 +343,7 @@ define([
let initGallery = (newElements) => {
if( newElements.length > 0){
// We have to add ALL thumbnail elements to the gallery!
// -> even those wthat are invisible (not lazyLoaded) now!
// -> even those which are invisible (not lazyLoaded) now!
// -> This is required for "swipe" through all images
let allThumbLinks = getThumbnailElements();
@@ -814,7 +814,7 @@ define([
// init carousel
initCarousel();
// init scrollspy
// init scrollSpy
// -> after "Carousel"! required for correct "viewport" calculation (Gallery)!
initScrollSpy();

View File

@@ -79,15 +79,21 @@ define([
let reason = status + ' ' + jqXHR.status + ': ' + error;
let errorData = [];
let redirect = false; // redirect user to other page e.g. login
let reload = true; // reload current page (default: true)
if(jqXHR.responseJSON){
// handle JSON
let errorObj = jqXHR.responseJSON;
let responseObj = jqXHR.responseJSON;
if(
errorObj.error &&
errorObj.error.length > 0
responseObj.error &&
responseObj.error.length > 0
){
errorData = errorObj.error;
errorData = responseObj.error;
}
if(responseObj.reroute){
redirect = responseObj.reroute;
}
}else{
// handle HTML
@@ -98,7 +104,13 @@ define([
}
console.error(' ↪ %s Error response: %o', jqXHR.url, errorData);
$(document).trigger('pf:shutdown', {status: jqXHR.status, reason: reason, error: errorData});
$(document).trigger('pf:shutdown', {
status: jqXHR.status,
reason: reason,
error: errorData,
redirect: redirect,
reload: reload
});
};
// map init functions =========================================================================================

View File

@@ -787,12 +787,14 @@ define([
label: '<i class="fas fa-fw fa-sync"></i> restart',
className: ['btn-primary'].join(' '),
callback: function(){
// check if error was 5xx -> reload page
// -> else try to logout -> ajax request
if(data.status >= 500 && data.status < 600){
// redirect to login
window.location = '../';
if(data.redirect) {
// ... redirect user to e.g. login form page ...
Util.redirect(data.redirect, ['logout']);
}else if(data.reload){
// ... or reload current page ...
location.reload();
}else{
// ... fallback try to logout user
documentElement.trigger('pf:menuLogout');
}
}
@@ -811,12 +813,9 @@ define([
};
// add error information (if available)
if(
data.error &&
data.error.length
){
for(let i = 0; i < data.error.length; i++){
options.content.textSmaller.push(data.error[i].message);
if(data.error && data.error.length){
for(let error of data.error){
options.content.textSmaller.push(error.message);
}
}

View File

@@ -76,7 +76,7 @@ define([
dialogElement.find('.modal-content').hideLoadingAnimation();
if(responseData.reroute !== undefined){
Util.redirect(responseData.reroute, []);
Util.redirect(responseData.reroute);
}else if(
responseData.error &&
responseData.error.length > 0

View File

@@ -753,7 +753,7 @@ define([
* show activity stats dialog
*/
$.fn.showStatsDialog = function(){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'datatables.loader'], function(template, Mustache){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'datatables.loader'], (template, Mustache) => {
// get current statistics map settings
let logActivityEnabled = false;
let activeMap = Util.getMapModule().getActiveMap();

View File

@@ -12,7 +12,8 @@ define([
let config = {
// Select2
resultOptionImageClass: 'pf-result-image' // class for Select2 result option entry with image
resultOptionImageClass: 'pf-result-image', // class for Select2 result option entry with image
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad' // class for Select2 result images that should be lazy loaded
};
/**
@@ -60,7 +61,7 @@ define([
}
if(imagePath){
thumb = '<img src="' + imagePath + '" style="max-width: 100%" />';
thumb = '<img class="' + config.select2ImageLazyLoadClass + '" data-original="' + imagePath + '" style="max-width: 100%"/>';
}else if(iconName){
thumb = '<i class="fas fa-fw ' + iconName + '" ></i>';
}
@@ -564,6 +565,7 @@ define([
return group;
});
},*/
disabled: options.hasOwnProperty('disabled') ? options.disabled : false,
allowClear: options.maxSelectionLength <= 1,
maximumSelectionLength: options.maxSelectionLength,
templateResult: formatCategoryTypeResultData
@@ -605,6 +607,7 @@ define([
return {
id: type.id,
text: type.name,
mass: type.hasOwnProperty('mass') ? type.mass : null,
groupId: this.groupId,
categoryId: this.categoryId,
categoryType: this.categoryType

View File

@@ -6,8 +6,9 @@ define([
'jquery',
'app/init',
'app/util',
'bootbox',
'app/map/util'
], ($, Init, Util, MapUtil) => {
], ($, Init, Util, bootbox, MapUtil) => {
'use strict';
let config = {
@@ -46,9 +47,17 @@ define([
connectionInfoTableCellMassLeftClass: 'pf-connection-info-mass-left', // class for "mass left" table cell
// dataTable
tableToolbarCondensedClass: 'pf-dataTable-condensed-toolbar', // class for condensed table toolbar
connectionInfoTableClass: 'pf-connection-info-table', // class for connection tables
tableCellImageClass: 'pf-table-image-cell', // class for table "image" cells
tableCellCounterClass: 'pf-table-counter-cell', // class for table "counter" cells
tableCellActionClass: 'pf-table-action-cell', // class for "action" cells
// connection dialog
connectionDialogId: 'pf-connection-info-dialog', // id for "connection" dialog
typeSelectId: 'pf-connection-info-dialog-type-select', // id for "ship type" select
shipMassId: 'pf-connection-info-dialog-mass', // id for "ship mass" input
characterSelectId: 'pf-connection-info-dialog-character-select', // id for "character" select
// config
showShip: true // default for "show current ship mass" toggle
@@ -511,6 +520,34 @@ define([
return moduleElement.find('.' + config.connectionInfoPanelClass).not('#' + getConnectionElementId(0));
};
/**
* enrich connectionData with "logs" data (if available) and other "missing" data
* @param connectionsData
* @param newConnectionsData
* @returns {*}
*/
let enrichConnectionsData = (connectionsData, newConnectionsData) => {
for(let i = 0; i < connectionsData.length; i++){
for(let newConnectionData of newConnectionsData){
if(connectionsData[i].id === newConnectionData.id){
// copy some missing data
connectionsData[i].character = newConnectionData.character;
connectionsData[i].created = newConnectionData.created;
// check for mass logs and copy data
if(newConnectionData.logs && newConnectionData.logs.length){
connectionsData[i].logs = newConnectionData.logs;
}
// check for signatures and copy data
if(newConnectionData.signatures && newConnectionData.signatures.length){
connectionsData[i].signatures = newConnectionData.signatures;
}
break;
}
}
}
return connectionsData;
};
/**
* request connection log data
* @param requestData
@@ -530,24 +567,7 @@ define([
dataType: 'json',
context: context
}).done(function(connectionsData){
// enrich connectionData with "logs" data (if available) and other "missing" data
for(let i = 0; i < this.connectionsData.length; i++){
for(let connectionData of connectionsData){
if(this.connectionsData[i].id === connectionData.id){
// copy some missing data
this.connectionsData[i].created = connectionData.created;
// check for mass logs and copy data
if(connectionData.logs && connectionData.logs.length){
this.connectionsData[i].logs = connectionData.logs;
}
// check for signatures and copy data
if(connectionData.signatures && connectionData.signatures.length){
this.connectionsData[i].signatures = connectionData.signatures;
}
break;
}
}
}
this.connectionsData = enrichConnectionsData(this.connectionsData, connectionsData);
callback(this.moduleElement, this.connectionsData);
}).always(function(){
@@ -592,9 +612,9 @@ define([
*/
let addConnectionsData = (moduleElement, connectionsData) => {
let getRowIndexesByData = (dataTable, colName, value) => {
return dataTable.rows().eq(0).filter((rowIdx) => {
return (dataTable.cell(rowIdx, colName + ':name').data() === value);
let getRowIndexesByData = (tableApi, colName, value) => {
return tableApi.rows().eq(0).filter((rowIdx) => {
return (tableApi.cell(rowIdx, colName + ':name').data() === value);
});
};
@@ -607,42 +627,47 @@ define([
connectionInfoElement.data('connectionData', connectionData);
// update dataTable ---------------------------------------------------------------
let dataTable = connectionElement.find('.dataTable').dataTable().api();
let tableApi = connectionElement.find('.dataTable').dataTable().api();
if(connectionData.logs && connectionData.logs.length > 0){
for(let i = 0; i < connectionData.logs.length; i++){
let rowData = connectionData.logs[i];
let row = null;
let rowNew = null;
let animationStatus = null;
let indexes = getRowIndexesByData(dataTable, 'index', rowData.id);
let indexes = getRowIndexesByData(tableApi, 'index', rowData.id);
if(indexes.length === 0){
// row not found -> add new row
row = dataTable.row.add( rowData );
rowNew = tableApi.row.add(rowData);
animationStatus = 'added';
}
/* else{
// we DON´t expect changes -> no row update)
}else{
// update row with FIRST index
//row = dataTable.row( parseInt(indexes[0]) );
// update row data
//row.data(connectionData.logs[i]);
//animationStatus = 'changed';
} */
let row = tableApi.row( parseInt(indexes[0]));
let rowDataCurrent = row.data();
// check if row data changed
if(rowDataCurrent.updated.updated !== rowData.updated.updated){
// ... row changed -> delete old and re-add
// -> cell actions might have changed
row.remove();
rowNew = tableApi.row.add(rowData);
animationStatus = 'changed';
}
}
if(
animationStatus !== null &&
row.length > 0
rowNew.length > 0
){
row.nodes().to$().data('animationStatus', animationStatus);
rowNew.nodes().to$().data('animationStatus', animationStatus);
}
}
}else{
// clear table or leave empty
dataTable.clear();
tableApi.clear();
}
// redraw dataTable
dataTable.draw(false);
tableApi.draw(false);
}
}
};
@@ -663,11 +688,45 @@ define([
let table = $('<table>', {
class: ['compact', 'stripe', 'order-column', 'row-border', 'nowrap', config.connectionInfoTableClass].join(' ')
}).append('<tfoot><tr><th></th><th></th><th></th><th></th><th></th></tr></tfoot>');
}).append('<tfoot><tr><th></th><th></th><th></th><th></th><th></th><th></th><th></th></tr></tfoot>');
connectionElement.append(table);
// init empty table
let logTable = table.DataTable({
dom: '<"container-fluid"' +
'<"row ' + config.tableToolbarCondensedClass + '"' +
'<"col-xs-5"i><"col-xs-5"p><"col-xs-2 text-right"B>>' +
'<"row"tr>>',
buttons: {
name: 'tableTools',
buttons: [
{
name: 'addLog',
className: config.moduleHeadlineIconClass,
text: '<i class="fa fa-plus"></i>',
action: function(e, tableApi, node, conf){
let logData = {};
// pre-fill form with current character data (if available)
let currentUserData = Util.getCurrentUserData();
if(currentUserData && currentUserData.character){
logData.character = {
id: currentUserData.character.id,
name: currentUserData.character.name
};
if(currentUserData.character.log){
logData.ship = {
id: currentUserData.character.log.ship.typeId,
name: currentUserData.character.log.ship.typeName
};
}
}
showLogDialog(moduleElement, connectionElement, connectionData, logData);
}
}
]
},
pageLength: 8,
paging: true,
pagingType: 'simple',
@@ -678,11 +737,14 @@ define([
searching: false,
hover: false,
autoWidth: false,
// rowId: 'systemTo',
language: {
emptyTable: 'No jumps recorded',
info: '_START_ to _END_ of _MAX_',
infoEmpty: ''
info: '_START_ - _END_ of _MAX_',
infoEmpty: '',
paginate: {
previous: '',
next: ''
}
},
columnDefs: [
{
@@ -692,10 +754,25 @@ define([
orderable: false,
searchable: false,
width: 20,
class: 'text-center',
data: 'id'
className: ['text-center', 'txt-color'].join(' '),
data: 'id',
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
if(
!rowData.record ||
(rowData.updated.updated !== rowData.created.created)
){
// log was manually modified or added
$(cell)
.addClass(Util.config.helpClass)
.addClass( 'txt-color-orange').tooltip({
container: 'body',
title: 'added/updated manually'
});
}
}
},{
targets: 1,
name: 'ship',
title: '',
width: 26,
orderable: false,
@@ -715,16 +792,17 @@ define([
}
},{
targets: 2,
name: 'character',
title: '',
width: 26,
orderable: false,
className: [Util.config.helpDefaultClass, 'text-center', config.tableCellImageClass].join(' '),
data: 'created.character',
data: 'character',
render: {
_: function(data, type, row){
let value = data.name;
_: (cellData, type, rowData, meta) => {
let value = cellData.name;
if(type === 'display'){
value = '<img src="' + Init.url.ccpImageServer + '/Character/' + data.id + '_32.jpg" title="' + value + '" data-toggle="tooltip" />';
value = '<img src="' + Init.url.ccpImageServer + '/Character/' + cellData.id + '_32.jpg" title="' + value + '" data-toggle="tooltip" />';
}
return value;
}
@@ -734,20 +812,26 @@ define([
}
},{
targets: 3,
name: 'mass',
title: 'mass',
className: ['text-right'].join(' ') ,
data: 'ship.mass',
render: {
_: function(data, type, row){
let value = data;
_: (cellData, type, rowData, meta) => {
let value = cellData;
if(type === 'display'){
value = Util.formatMassValue(value);
if(!rowData.active){
// log is "deleted"
value = '<span class="pf-font-line-through txt-color txt-color-red">' + value + '</span>';
}
}
return value;
}
}
},{
targets: 4,
name: 'created',
title: 'log',
width: 55,
className: ['text-right', config.tableCellCounterClass].join(' '),
@@ -755,6 +839,108 @@ define([
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter('d');
}
},{
targets: 5,
name: 'edit',
title: '',
orderable: false,
searchable: false,
width: 10,
className: ['text-center', config.tableCellActionClass, config.moduleHeadlineIconClass].join(' '),
data: null,
render: {
display: data => {
let icon = '';
if(data.active){
icon = '<i class="fas fa-pen"></i>';
}
return icon;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if($(cell).is(':empty')){
$(cell).removeClass(config.tableCellActionClass + ' ' + config.moduleHeadlineIconClass);
}else{
$(cell).on('click', function(e){
showLogDialog(moduleElement, connectionElement, connectionData, rowData);
});
}
}
},{
targets: 6,
name: 'delete',
title: '',
orderable: false,
searchable: false,
width: 10,
className: ['text-center', config.tableCellActionClass].join(' '),
data: 'active',
render: {
display: data => {
let val = '<i class="fas fa-plus"></i>';
if(data){
val = '<i class="fas fa-times txt-color txt-color-redDarker"></i>';
}
return val;
}
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
let tableApi = this.api();
if(rowData.active){
let confirmationSettings = {
container: 'body',
placement: 'left',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'delete jump log',
btnOkClass: 'btn btn-sm btn-danger',
btnOkLabel: 'delete',
btnOkIcon: 'fas fa-fw fa-times',
onConfirm : function(e, target){
// get current row data (important!)
// -> "rowData" param is not current state, values are "on createCell()" state
rowData = tableApi.row($(cell).parents('tr')).data();
connectionElement.find('table').showLoadingAnimation();
request('DELETE', 'log', rowData.id, {}, {
connectionElement: connectionElement
}, requestAlways)
.then(
payload => {
addConnectionsData(moduleElement, enrichConnectionsData([connectionData], payload.data));
},
Util.handleAjaxErrorResponse
);
}
};
// init confirmation dialog
$(cell).confirmation(confirmationSettings);
}else {
$(cell).on('click', function(e){
connectionElement.find('table').showLoadingAnimation();
let requestData = {
active: 1
};
request('PATCH', 'log', rowData.id, requestData, {
connectionElement: connectionElement
}, requestAlways)
.then(
payload => {
addConnectionsData(moduleElement, enrichConnectionsData([connectionData], payload.data));
},
Util.handleAjaxErrorResponse
);
});
}
}
}
],
drawCallback: function(settings){
@@ -772,27 +958,34 @@ define([
},
footerCallback: function(row, data, start, end, display ){
let api = this.api();
let sumColumnIndexes = [3];
let tableApi = this.api();
let sumColumnIndexes = ['mass:name', 'delete:name'];
// column data for "sum" columns over this page
let pageTotalColumns = api
let pageTotalColumns = tableApi
.columns( sumColumnIndexes, { page: 'all'} )
.data();
// sum columns for "total" sum
pageTotalColumns.each((colData, index) => {
pageTotalColumns[index] = colData.reduce((a, b) => {
return parseInt(a) + parseInt(b);
pageTotalColumns.each((colData, colIndex) => {
pageTotalColumns[colIndex] = colData.reduce((sum, val, rowIndex) => {
// sum "mass" (colIndex 0) only if not "deleted" (colIndex 1)
if(colIndex === 0 && pageTotalColumns[1][rowIndex]){
return sum + parseInt(val);
}else{
return sum;
}
}, 0);
});
$(sumColumnIndexes).each((index, value) => {
$( api.column( value ).footer() ).text( Util.formatMassValue(pageTotalColumns[index]) );
sumColumnIndexes.forEach((colSelector, index) => {
// only "mass" column footer needs updates
if(colSelector === 'mass:name'){
$(tableApi.column(colSelector).footer()).text( Util.formatMassValue(pageTotalColumns[index]) );
// save mass for further reCalculation of "info" table
connectionElement.find('.' + config.connectionInfoTableCellMassLogClass).data('mass', pageTotalColumns[index]);
// save mass for further reCalculation of "info" table
connectionElement.find('.' + config.connectionInfoTableCellMassLogClass).data('mass', pageTotalColumns[index]);
}
});
// calculate "info" table -----------------------------------------------------
@@ -805,14 +998,186 @@ define([
logTable.on('order.dt search.dt', function(){
let pageInfo = logTable.page.info();
logTable.column(0, {search:'applied', order:'applied'}).nodes().each((cell, i) => {
let content = (pageInfo.recordsTotal - i) + '.&nbsp;&nbsp;';
logTable.column('index:name', {search:'applied', order:'applied'}).nodes().each((cell, i) => {
let content = (pageInfo.recordsTotal - i) + '.';
$(cell).html(content);
});
});
}
};
let request = (action, entity, ids = [], data = {}, context = {}, always = null) => {
let requestExecutor = (resolve, reject) => {
let payload = {
action: 'request',
name: action.toLowerCase() + entity.charAt(0).toUpperCase() + entity.slice(1)
};
// build request url --------------------------------------------------------------------------------------
let url = Init.path.api + '/' + entity;
let path = '';
if(isNaN(ids)){
if(Array.isArray(ids)){
path += '/' + ids.join(',');
}
}else{
let id = parseInt(ids, 10);
path += id ? '/' + id : '';
}
url += path;
$.ajax({
type: action,
url: url,
data: JSON.stringify(data),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
context: context
}).done(function(response){
payload.data = response;
payload.context = this;
resolve(payload);
}).fail(function(jqXHR, status, error){
payload.data = {
jqXHR: jqXHR,
status: status,
error: error
};
payload.context = this;
reject(payload);
}).always(function(){
if(always){
always(this);
}
});
};
return new Promise(requestExecutor);
};
/**
*
* @param context
*/
let requestAlways = (context) => {
context.connectionElement.find('table').hideLoadingAnimation();
};
/**
* show jump log dialog
* @param moduleElement
* @param connectionElement
* @param connectionData
* @param logData
*/
let showLogDialog = (moduleElement, connectionElement, connectionData, logData = {}) => {
let data = {
id: config.connectionDialogId,
typeSelectId: config.typeSelectId,
shipMassId: config.shipMassId,
characterSelectId: config.characterSelectId,
logData: logData,
massFormat: () => {
return (val, render) => {
return (parseInt(render(val) || 0) / 1000) || '';
};
}
};
requirejs(['text!templates/dialog/connection_log.html', 'mustache'], (template, Mustache) => {
let content = Mustache.render(template, data);
let connectionDialog = bootbox.dialog({
title: 'Jump log',
message: content,
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
},
success: {
label: '<i class="fas fa-fw fa-check"></i>&nbsp;save',
className: 'btn-success',
callback: function(){
let form = this.find('form');
// validate form
form.validator('validate');
// check whether the form is valid
let formValid = form.isValidForm();
if(formValid){
// get form data
let formData = form.getFormValues();
formData.id = Util.getObjVal(logData, 'id') || 0;
formData.connectionId = Util.getObjVal(connectionData, 'id') || 0;
formData.shipTypeId = Util.getObjVal(formData, 'shipTypeId') || 0;
formData.shipMass = parseInt((Util.getObjVal(formData, 'shipMass') || 0) * 1000);
formData.characterId = Util.getObjVal(formData, 'characterId') || 0;
// we need some "additional" form data from the Select2 dropdown
// -> data is required on the backend side
let formDataShip = form.find('#' + config.typeSelectId).select2('data');
let formDataCharacter = form.find('#' + config.characterSelectId).select2('data');
formData.shipTypeName = formDataShip.length ? formDataShip[0].text : '';
formData.characterName = formDataCharacter.length ? formDataCharacter[0].text : '';
let method = formData.id ? 'PATCH' : 'PUT';
request(method, 'log', formData.id, formData, {
connectionElement: connectionElement,
formElement: form
}, requestAlways)
.then(
payload => {
addConnectionsData(moduleElement, enrichConnectionsData([connectionData], payload.data));
this.modal('hide');
},
Util.handleAjaxErrorResponse
);
}
return false;
}
}
}
});
connectionDialog.on('show.bs.modal', function(e){
let modalContent = $('#' + config.connectionDialogId);
// init type select live search
let selectElementType = modalContent.find('#' + config.typeSelectId);
selectElementType.initUniverseTypeSelect({
categoryIds: [6],
maxSelectionLength: 1,
selected: [Util.getObjVal(logData, 'ship.typeId')]
}).on('select2:select select2:unselecting', function(e){
// get ship mass from selected ship type and update mass input field
let shipMass = e.params.data ? e.params.data.mass / 1000 : '';
modalContent.find('#' + config.shipMassId).val(shipMass);
});
// init character select live search
let selectElementCharacter = modalContent.find('#' + config.characterSelectId);
selectElementCharacter.initUniverseSearch({
categoryNames: ['character'],
maxSelectionLength: 1
});
});
// show dialog
connectionDialog.modal('show');
});
};
/**
* remove connection Panel from moduleElement
* @param connectionElement

View File

@@ -337,7 +337,7 @@ define([
data: statusData
});
// init character counter
// init char counter
let textarea = modalContent.find('#' + config.descriptionTextareaId);
let charCounter = modalContent.find('.' + config.descriptionTextareaCharCounter);
Util.updateCounter(textarea, charCounter, maxDescriptionLength);

View File

@@ -8,6 +8,7 @@ define([
'conf/signature_type',
'bootbox',
'localForage',
'lazyload',
'velocity',
'velocityUI',
'customScrollbar',
@@ -70,6 +71,7 @@ define([
// select2
select2Class: 'pf-select2', // class for all "Select2" <select> elements
select2ImageLazyLoadClass: 'pf-select2-image-lazyLoad',
// animation
animationPulseSuccessClass: 'pf-animation-pulse-success', // animation class
@@ -1059,6 +1061,9 @@ define([
// in order to make mCustomScrollbar mouseWheel enable works correctly
$(resultsWrapper).find('ul.select2-results__options').off('mousewheel');
// preload images that are not visible yet
let lazyLoadImagesOffset = 240;
resultsWrapper.mCustomScrollbar({
mouseWheel: {
enable: true,
@@ -1085,10 +1090,35 @@ define([
// -> this is because the initPassiveEvents() delegates the mouseWheel events
togglePageScroll(false);
},
onUpdate: function(a){
// whenever the scroll content updates -> init lazyLoad for potential images
$('.' + config.select2ImageLazyLoadClass).lazyload({
container: this,
threshold: lazyLoadImagesOffset,
event: 'pf:lazyLoad'
});
},
onTotalScroll: function(){
// we want to "trigger" Select2´s 'scroll' event
// in order to make its "infinite scrolling" function working
this.mcs.content.find(':first-child').trigger('scroll');
},
whileScrolling: function(){
// lazy load for images -> reduce number of calculations by % 10
if(0 === this.mcs.top % 10){
let scroller = $(this).find('.mCSB_container');
let scrollerBox = scroller.closest('.mCustomScrollBox');
scrollerBox.find('.' + config.select2ImageLazyLoadClass).filter(function(){
let $this = $(this);
if($this.attr('src') === $this.attr('data-original')) return false;
let scrollerTop = scroller.position().top;
let scrollerHeight = scrollerBox.height();
let offset = $this.closest('div').position();
return (offset.top - lazyLoadImagesOffset < scrollerHeight - scrollerTop);
}).trigger('pf:lazyLoad');
}
}
}
});
@@ -1108,7 +1138,7 @@ define([
return wrapper;
};
// global open event
// global opened event
$(document).on('select2:open', '.' + config.select2Class, function(e){
let resultsWrapper = getResultsWrapper(this);
if(resultsWrapper){
@@ -1497,6 +1527,37 @@ define([
});
};
/**
* global ajax error handler -> handles .fail() requests
* @param payload
*/
let handleAjaxErrorResponse = (payload) => {
// handle only request errors
if(payload.action === 'request'){
let jqXHR = payload.data.jqXHR;
let reason = '';
if(jqXHR.responseJSON){
// ... valid JSON response
let response = jqXHR.responseJSON;
if(response.error && response.error.length > 0){
// build error notification reason from errors
reason = response.error.map(error => error.status).join('\n');
// check if errors might belong to a HTML form -> check "context"
if(payload.context.formElement){
// show form messages e.g. validation errors
payload.context.formElement.showFormMessage(response.error);
}
}
}else{
reason = 'Invalid JSON response';
}
showNotify({title: jqXHR.status + ': ' + payload.name, text: reason, type: 'error'});
}
};
/**
* get WebSocket readyState description from ID
* https://developer.mozilla.org/de/docs/Web/API/WebSocket
@@ -2864,7 +2925,7 @@ define([
* @param url
* @param params
*/
let redirect = (url, params) => {
let redirect = (url, params = []) => {
let currentUrl = document.URL;
if(url !== currentUrl){
@@ -2976,6 +3037,7 @@ define([
stopTabBlink: stopTabBlink,
getLogInfo: getLogInfo,
ajaxSetup: ajaxSetup,
handleAjaxErrorResponse: handleAjaxErrorResponse,
setSyncStatus: setSyncStatus,
getSyncType: getSyncType,
isXHRAborted: isXHRAborted,

View File

@@ -1,2 +1,2 @@
/*! Lazy Load 1.9.5 - MIT license - Copyright 2010-2015 Mika Tuupola */
/*! Lazy Load 1.9.7 - MIT license - Copyright 2010-2015 Mika Tuupola */
!function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!1,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("<img />").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document);

View File

@@ -0,0 +1,51 @@
<div id="{{id}}">
<form role="form" class="form-horizontal">
<div class="row">
<div class="col-xs-9 col-sm-7">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{typeSelectId}}">Ship</label>
<div class="col-sm-10">
<div class="input-group">
<label for="{{typeSelectId}}"></label>
<select id="{{typeSelectId}}" class="pf-select2" name="shipTypeId" data-error="Ship type is required" required {{#logData.id}}readonly{{/logData.id}}></select>
<span class="note help-block with-errors"></span>
</div>
</div>
</div>
</div>
<div class="col-xs-3 col-sm-5">
<div class="form-group">
<label class="col-sm-6 control-label" for="{{shipMassId}}">Jump mass</label>
<div class="col-sm-6">
<div class="input-icon-right">
<i class="fas">t</i>
<input id="{{shipMassId}}" class="form-control text-right" name="shipMass" type="number" value="{{#massFormat}}{{logData.ship.mass}}{{/massFormat}}" min="32" max="2400000" step="1" data-error="Mass required" data-min-error="a POD has 32t" data-max-error="a Titan has 2.400.000t" required>
</div>
<div class="note help-block with-errors"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-9 col-sm-7">
<div class="form-group">
<label class="col-sm-2 control-label" for="{{characterSelectId}}">Character</label>
<div class="col-sm-10">
<div class="input-group">
<label for="{{characterSelectId}}"></label>
<select id="{{characterSelectId}}" class="pf-select2" name="characterId" data-error="Character is required" required {{#logData.id}}readonly{{/logData.id}}>
{{#logData.character}}
<option value="{{id}}" selected="selected">{{name}}</option>
{{/logData.character}}
</select>
<span class="note help-block with-errors"></span>
</div>
</div>
</div>
</div>
</div>
</form>
</div>

View File

@@ -43,7 +43,7 @@
{{#hasRightMapUpdate}}
<form role="form" class="form-horizontal">
<div class="row ">
<div class="row">
<div class="col-sm-6">
<h4 class="pf-dynamic-area">Map options</h4>
@@ -116,7 +116,7 @@
<div id="pf-map-dialog-slack-section" class="collapse {{#slackSectionShow}}in{{/slackSectionShow}}">
<fieldset {{^slackEnabled}}disabled{{/slackEnabled}}>
<div class="row ">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-6">
<div class="form-group">
<label for="{{slackWebHookURLId}}" class="col-sm-3 col-md-4 control-label">WebHook
@@ -155,15 +155,14 @@
</label>
<div class="col-sm-6 col-md-8">
<div class="input-icon-left input-icon-right">
<span class="fa-stack">
<i class="fas fa-fw fa-lg fa-ellipsis-v fa-stack-1x"></i>
<i class="fas fa-fw fa-2x fa-minus fa-stack-1x"></i>
</span>
<span class="fa-stack">
<i class="fas fa-fw fa-lg fa-ellipsis-v fa-stack-1x"></i>
<i class="fas fa-fw fa-2x fa-minus fa-stack-1x"></i>
</span>
<i class="fas fa-fw fa-lg fa-ellipsis-v fa-stack-1x"></i>
<i class="fas fa-fw fa-2x fa-minus fa-stack-1x"></i>
</span>
<span class="fa-stack">
<i class="fas fa-fw fa-lg fa-ellipsis-v fa-stack-1x"></i>
<i class="fas fa-fw fa-2x fa-minus fa-stack-1x"></i>
</span>
<input name="slackIcon" type="text" class="form-control text-center" id="{{slackIconId}}" value="{{slackIcon}}" placeholder="slack" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)">
</div>
<div class="note help-block with-errors"></div>
@@ -181,23 +180,24 @@
</label>
<div class="col-sm-6 col-md-8">
<div class="input-icon-left" {{^slackRallyEnabled}}title="Globally disabled for this map type"{{/slackRallyEnabled}}>
<i class="fas fa-hashtag"></i>
<input name="slackChannelRally" type="text" class="form-control" id="{{slackChannelRallyId}}" value="{{slackChannelRally}}" placeholder="map_rally" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)" {{^slackRallyEnabled}}disabled{{/slackRallyEnabled}}>
<i class="fas fa-hashtag"></i>
<input name="slackChannelRally" type="text" class="form-control" id="{{slackChannelRallyId}}" value="{{slackChannelRally}}" placeholder="map_rally" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)" {{^slackRallyEnabled}}disabled{{/slackRallyEnabled}}>
</div>
</div>
<div class="note help-block with-errors"></div>
</div>
</div>
</div>
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<label for="{{slackChannelHistoryId}}" class="col-sm-6 col-md-4 control-label">Channel 'map changes'
<i class="fas fa-fw fa-question-circle pf-help-light" title="Send map changes to a Slack channel. This can be used in addition or replacement for the logfiles. (check 'Map logging' section above)"></i>
</label>
<div class="col-sm-6 col-md-8">
<div class="input-icon-left" {{^slackHistoryEnabled}}title="Globally disabled for this map type"{{/slackHistoryEnabled}}>
<i class="fas fa-hashtag"></i>
<input name="slackChannelHistory" type="text" class="form-control" id="{{slackChannelHistoryId}}" value="{{slackChannelHistory}}" placeholder="map_logging" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)" {{^slackHistoryEnabled}}disabled{{/slackHistoryEnabled}}>
<div class="col-xs-6 col-sm-6 col-md-6">
<div class="form-group">
<label for="{{slackChannelHistoryId}}" class="col-sm-6 col-md-4 control-label">Channel 'map changes'
<i class="fas fa-fw fa-question-circle pf-help-light" title="Send map changes to a Slack channel. This can be used in addition or replacement for the logfiles. (check 'Map logging' section above)"></i>
</label>
<div class="col-sm-6 col-md-8">
<div class="input-icon-left" {{^slackHistoryEnabled}}title="Globally disabled for this map type"{{/slackHistoryEnabled}}>
<i class="fas fa-hashtag"></i>
<input name="slackChannelHistory" type="text" class="form-control" id="{{slackChannelHistoryId}}" value="{{slackChannelHistory}}" placeholder="map_logging" pattern="^[_\-A-z0-9]{1,}$" data-error="Allowed chars (A-z, 0-9, _, -)" {{^slackHistoryEnabled}}disabled{{/slackHistoryEnabled}}>
</div>
</div>
<div class="help-block with-errors"></div>
</div>
@@ -219,7 +219,7 @@
<div id="pf-map-dialog-discord-section" class="collapse {{#discordSectionShow}}in{{/discordSectionShow}}">
<fieldset {{^discordEnabled}}disabled{{/discordEnabled}}>
<div class="row ">
<div class="row">
<div class="col-xs-12 col-sm-12 col-md-6">
<div class="form-group">
<label for="{{discordWebHookURLRallyId}}" class="col-sm-3 col-md-4 control-label">WebHook 'rally point'

View File

@@ -339,7 +339,7 @@ input[type="email"]{
* WITH ICON
*/
.input-icon-right {
//position:relative; why?
position:relative;
}
.input-icon-right > i, .input-icon-left > i {
position:absolute;

View File

@@ -1,5 +1,4 @@
.modal-content, .panel-body {
// dialog headline ==========================================================
h2 {
font-family: $font-family-sans-serif;
@@ -59,7 +58,6 @@
}
}
.modal-content {
// data tables ==============================================================
.dataTables_wrapper{
@@ -142,13 +140,11 @@
}
}
}
}
// settings dialog ============================================================
#pf-settings-dialog{
// smaller buttons
.form-group .btn-sm{
padding: 4px 7px 3px;
@@ -226,6 +222,13 @@
}
}
// connection log dialog ======================================================
#pf-connection-info-dialog{
#pf-connection-info-dialog-type-select, #pf-connection-info-dialog-character-select{
width: 310px !important;
}
}
// jump info dialog ===========================================================
.pf-jump-info-dialog{
blockquote{
@@ -233,6 +236,7 @@
margin-bottom: 5px;
}
}
// changelog
.pf-changelog-dialog{
.pf-dynamic-message-container{
@@ -242,7 +246,6 @@
// credits dialog =============================================================
.pf-credits-dialog{
.pf-credits-logo-background{
overflow: visible;
background: url("#{$base-url}/logo_bg.png");

View File

@@ -37,6 +37,16 @@ input, select{
}
}
input[type='number']{
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
margin: 0;
}
}
textarea{
min-height: 32px;
max-height: 400px;

View File

@@ -258,7 +258,7 @@ select:active, select:hover {
margin: 0 3px; // <select> for page size
padding: 1px;
}
.dt-buttons{
.dt-button{
line-height: 22px;
@@ -275,6 +275,53 @@ select:active, select:hover {
}
}
// condensed dataTable toolbar
.pf-dataTable-condensed-toolbar{
padding-top: 3px;
.dataTables_info{
padding-top: 0;
text-align: left;
}
& > div{
padding-left: 5px;
padding-right: 5px;
}
.dataTables_paginate{
padding-top: 0;
margin-top: 0;
.paginate_button{
padding: 0;
height: 13px;
border: 0 !important;
&:before, &:after{
top: 0;
}
&:hover{
background: transparent !important;
color: $orange !important;
}
}
}
.dt-buttons{
text-align: right;
margin-bottom: 0;
.dt-button{
line-height: 18px;
.fa{
margin-right: 0;
}
}
}
}
.dataTable{
th{
&.pf-table-image-cell,

View File

@@ -215,10 +215,6 @@
td{
text-transform: capitalize;
& > .fas, & > .far{
font-size: 10px;
}
.fa-sync, .fa-search{
@extend .pf-dialog-icon-button;
@@ -374,7 +370,7 @@
// connection info module ==================================================
.pf-connection-info-module{
.row{
& > .row{
display: flex;
align-items: stretch;
flex-wrap: wrap;
@@ -385,8 +381,8 @@
font-size: 10px;
td{
& > .fas, & > .far{
font-size: 10px;
.fa-plus{
@extend .pf-dialog-icon-button;
}
}
}

View File

@@ -91,9 +91,44 @@
&.select2-container--open .select2-dropdown {
border-color: $focus-border-color;
}
&.select2-container--disabled {
.select2-selection {
background-color: $gray;
cursor: not-allowed;
border: 1px solid $gray-light;
.select2-selection__arrow{
background-color: $gray;
}
.select2-selection__clear{
display: none;
}
}
}
}
// hide small "arrow" for hidden select elements
select.select2-hidden-accessible{
select.select2-hidden-accessible {
@include appearance( none );
}
select[readonly].select2-hidden-accessible {
+ .select2-container {
pointer-events: none;
touch-action: none;
.select2-selection {
background-color: $gray;
.select2-selection__arrow{
background-color: $gray;
}
.select2-selection__clear{
display: none;
}
}
}
}