- new "activity log" for user actions (create/update/delete) of (systems/connections/signatures), resolves #280, relates #271

- new added cronjob to delete old statistics data (older 1 year) (weekly)
- updated main menu structure (added new headlines,...)
- updated "feature page", added new section for "statistics"
- updated "system delete" function. systems with no changes (description/alias) will now get deleted instead of set "active = 0", #184
- changed max expire time from file caching (/tmp/cache) from 20d -> 10d
- changed "character log" TTL from 10s to 5s
- changed cronjob interval for "deleteExpiredData" from "weekly" to "downtime"
- changed "delete" icon (e.g. context menu on map) from "eraser" to "trash"
- removed cronjob output logging (cron_deactivateMapData.log)
- fixed non click-able character panels on login page, closed #332
This commit is contained in:
Exodus4D
2016-10-17 14:01:46 +02:00
parent 5e48cce5ab
commit b2cce3cde2
54 changed files with 2995 additions and 259 deletions

View File

@@ -17,15 +17,18 @@ sixthHour = */10 * * * *
halfHour = */30 * * * *
[CRON.jobs]
; delete character log data
deleteLogData = Cron\CharacterUpdate->deleteLogData, @sixthHour
; delete expired signatures
deleteSignatures = Cron\MapUpdate->deleteSignatures, @halfHour
; import system data (jump, kill,..) from CCP API
importSystemData = Cron\CcpSystemsUpdate->importSystemData, @hourly
; disable outdated maps
deactivateMapData = Cron\MapUpdate->deactivateMapData, @hourly
; delete character log data
deleteLogData = Cron\CharacterUpdate->deleteLogData, @sixthHour
; delete disabled maps
deleteMapData = Cron\MapUpdate->deleteMapData, @downtime
@@ -33,7 +36,7 @@ deleteMapData = Cron\MapUpdate->deleteMapData, @downtime
deleteAuthenticationData = Cron\CharacterUpdate->deleteAuthenticationData, @downtime
; delete expired cache files
deleteExpiredCacheData = Cron\Cache->deleteExpiredData, @weekly
deleteExpiredCacheData = Cron\Cache->deleteExpiredData, @downtime
; delete expired signatures
deleteSignatures = Cron\MapUpdate->deleteSignatures, @halfHour
; delete old statistics (activity log) data
deleteStatisticsData = Cron\StatisticsUpdate->deleteStatisticsData, @weekly

View File

@@ -10,7 +10,7 @@ namespace Controller\Api;
use Controller;
use Model;
class Connection extends Controller\AccessController{
class Connection extends Controller\AccessController {
/**
* @param \Base $f3

View File

@@ -174,6 +174,14 @@ class Map extends Controller\AccessController {
];
$return->maxSharedCount = $maxSharedCount;
// get activity log options per map ---------------------------------------------------------------------------
$activityLogging = [
'character' => $f3->get('PATHFINDER.MAP.PRIVATE.ACTIVITY_LOGGING'),
'corporation' => $f3->get('PATHFINDER.MAP.CORPORATION.ACTIVITY_LOGGING'),
'alliance' => $f3->get('PATHFINDER.MAP.ALLIANCE.ACTIVITY_LOGGING'),
];
$return->activityLogging = $activityLogging;
// get program routes -----------------------------------------------------------------------------------------
$return->routes = [
'ssoLogin' => $this->getF3()->alias( 'sso', ['action' => 'requestAuthorization'] )
@@ -223,11 +231,13 @@ class Map extends Controller\AccessController {
* @var $system Model\SystemModel
*/
$system = Model\BasicModel::getNew('SystemModel');
$system->setActivityLogging(false);
/**
* @var $connection Model\ConnectionModel
*/
$connection = Model\BasicModel::getNew('ConnectionModel');
$connection->setActivityLogging(false);
foreach($importData['mapData'] as $mapData){
if(

View File

@@ -7,6 +7,7 @@
*/
namespace Controller\Api;
use Controller;
use Model;
@@ -15,7 +16,7 @@ use Model;
* Class Route
* @package Controller\Api
*/
class Route extends \Controller\AccessController {
class Route extends Controller\AccessController {
/**
* cache time for static jump data (e.g. K-Space stargates)

View File

@@ -11,7 +11,7 @@ use Controller;
use Model;
class Signature extends Controller\AccessController{
class Signature extends Controller\AccessController {
/**
* event handler

View File

@@ -0,0 +1,291 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 03.10.2016
* Time: 00:29
*/
namespace controller\api;
use Controller;
use Model\CharacterModel;
class Statistic extends Controller\AccessController {
/**
* concat a year and calendar week number
* week numbers > 10 will get prefixed with "0"
* -> e.g. year 2016, week 2 => "201602"
* @param int $year
* @param int $week
* @return string
*/
protected function concatYearWeek($year, $week){
return strval($year) . str_pad($week, 2, 0, STR_PAD_LEFT);
}
/**
* get max count of weeks in a year
* @param $year
* @return int
*/
protected function getIsoWeeksInYear($year) {
$date = new \DateTime;
$date->setISODate($year, 53);
return ($date->format('W') === '53' ? 53 : 52);
}
/**
* get number of weeks for a given period
* @param string $period
* @param int $year
* @return int
*/
protected function getWeekCount($period, $year){
$weeksInYear = $this->getIsoWeeksInYear($year);
switch($period){
case 'yearly':
$weekCount = $weeksInYear;
break;
case 'monthly':
$weekCount = 4;
break;
case 'weekly':
default:
$weekCount = 1;
break;
}
return $weekCount;
}
/**
* calculate calendar week and year for a given offset (weekCount)
* -> count forward OR backward
* @param int $year
* @param int $week
* @param int $weekCount
* @param bool $backwards
* @return array
*/
protected function calculateYearWeekOffset($year, $week, $weekCount, $backwards = false){
$offset = [
'year' => (int)$year,
'week' => (int)$week
];
$weeksInYear = $this->getIsoWeeksInYear($year);
// just for security...
if($offset['week'] > $weeksInYear){
$offset['week'] = $weeksInYear;
}elseif($offset['week'] <= 0){
$offset['week'] = 1;
}
for($i = 1; $i < $weekCount; $i++){
if($backwards){
// calculate backward
$offset['week']--;
if($offset['week'] <= 0){
// year change -> reset yearWeeks
$offset['year']--;
$offset['week'] = $this->getIsoWeeksInYear($offset['year']);
}
}else{
// calculate forward
$offset['week']++;
if($offset['week'] > $weeksInYear){
// year change -> reset yearWeeks
$offset['week'] = 1;
$offset['year']++;
$weeksInYear = $this->getIsoWeeksInYear($offset['year']);
}
}
}
return $offset;
}
/**
* query statistic data for "activity log"
* -> group result by characterId
* @param CharacterModel $character
* @param int $typeId
* @param int $yearStart
* @param int $weekStart
* @param int $yearEnd
* @param int $weekEnd
* @return array
*/
protected function queryStatistic( CharacterModel $character, $typeId, $yearStart, $weekStart, $yearEnd, $weekEnd){
$data = [];
// can be either "characterId" || "corporationId" || "allianceId"
// -> is required (>0) to limit the result to only accessible data for the given character!
$objectId = 0;
// add map-"typeId" (private/corp/ally) condition -------------------------------------------------------------
// check if "ACTIVITY_LOGGING" is active for a given "typeId"
$sqlMapType = "";
switch($typeId){
case 2:
if( $this->getF3()->get('PATHFINDER.MAP.PRIVATE.ACTIVITY_LOGGING') ){
$sqlMapType .= " AND `character`.`id` = :objectId ";
$objectId = $character->_id;
}
break;
case 3:
if(
$this->getF3()->get('PATHFINDER.MAP.CORPORATION.ACTIVITY_LOGGING') &&
$character->hasCorporation()
){
$sqlMapType .= " AND `character`.`corporationId` = :objectId ";
$objectId = $character->get('corporationId', true);
}
break;
case 4:
if(
$this->getF3()->get('PATHFINDER.MAP.ALLIANCE.ACTIVITY_LOGGING') &&
$character->hasAlliance()
){
$sqlMapType .= " AND `character`.`allianceId` = :objectId ";
$objectId = $character->get('allianceId', true);
}
break;
}
if($objectId > 0){
$queryData = [
':active' => 1,
':objectId' => $objectId
];
// date offset condition ----------------------------------------------------------------------------------
$sqlDateOffset = " AND CONCAT(`log`.`year`, `log`.`week`) BETWEEN :yearWeekStart AND :yearWeekEnd ";
$queryData[':yearWeekStart'] = $this->concatYearWeek($yearStart, $weekStart);
$queryData[':yearWeekEnd'] = $this->concatYearWeek($yearEnd, $weekEnd);
// build query --------------------------------------------------------------------------------------------
$sql = "SELECT
`log`.`year`,
`log`.`week`,
`log`.`characterId`,
`character`.`name`,
`character`.`lastLogin`,
SUM(`log`.`systemCreate`) `systemCreate`,
SUM(`log`.`systemUpdate`) `systemUpdate`,
SUM(`log`.`systemDelete`) `systemDelete`,
SUM(`log`.`connectionCreate`) `connectionCreate`,
SUM(`log`.`connectionUpdate`) `connectionUpdate`,
SUM(`log`.`connectionDelete`) `connectionDelete`,
SUM(`log`.`signatureCreate`) `signatureCreate`,
SUM(`log`.`signatureUpdate`) `signatureUpdate`,
SUM(`log`.`signatureDelete`) `signatureDelete`
FROM
`activity_log` `log` INNER JOIN
`character` ON
`character`.`id` = `log`.`characterId`
WHERE
`log`.`active` = :active
" . $sqlMapType . "
" . $sqlDateOffset . "
GROUP BY
`log`.`year`,
`log`.`week`,
`log`.`characterId`
ORDER BY
`log`.`year` DESC, `log`.`week` DESC";
$result = $this->getDB()->exec($sql, $queryData);
if( !empty($result) ){
// group result by characterId
foreach ($result as $key => &$entry) {
$tmp = $entry;
unset($tmp['characterId']);
unset($tmp['name']);
unset($tmp['lastLogin']);
$data[$entry['characterId']]['name'] = $entry['name'];
$data[$entry['characterId']]['lastLogin'] = strtotime($entry['lastLogin']);
$data[$entry['characterId']]['weeks'][ $entry['year'] . $entry['week'] ] = $tmp;
}
}
}
return $data;
}
/**
* get statistics data
* @param \Base $f3
*/
public function getData(\Base $f3){
$postData = (array)$f3->get('POST');
$return = (object) [];
$period = $postData['period'];
$typeId = (int)$postData['typeId'];
$yearStart = (int)$postData['year'];
$weekStart = (int)$postData['week'];
$currentYear = (int)date('o');
$currentWeek = (int)date('W');
if(
$yearStart &&
$weekStart
){
$weekCount = $this->getWeekCount($period, $yearStart);
}else{
// if start date is not set -> calculate it from current data
$tmpYear = $currentYear;
if($period == 'yearly'){
$tmpYear--;
}
$weekCount = $this->getWeekCount($period, $tmpYear);
$offsetStart = $this->calculateYearWeekOffset($currentYear, $currentWeek, $weekCount, true);
$yearStart = $offsetStart['year'];
$weekStart = $offsetStart['week'];
}
// date offset for statistics query
$offset = $this->calculateYearWeekOffset($yearStart, $weekStart, $weekCount);
$activeCharacter = $this->getCharacter();
$return->statistics = $this->queryStatistic($activeCharacter, $typeId, $yearStart, $weekStart, $offset['year'], $offset['week']);
$return->period = $period;
$return->typeId = $typeId;
$return->weekCount = $weekCount;
$return->yearWeeks = [
$yearStart => $this->getIsoWeeksInYear($yearStart),
($yearStart + 1) => $this->getIsoWeeksInYear($yearStart + 1)
];
// pagination offset
$offsetNext = $this->calculateYearWeekOffset($yearStart, $weekStart, $weekCount + 1);
$offsetPrev = $this->calculateYearWeekOffset($yearStart, $weekStart, $weekCount + 1, true);
// check if "next" button is available (not in future)
$currentCurrentDataConcat = intval( $this->concatYearWeek($currentYear, $currentWeek) );
$offsetNextDateConcat = intval( $this->concatYearWeek($offsetNext['year'], $offsetNext['week']) );
if( $offsetNextDateConcat <= $currentCurrentDataConcat){
$return->next = $offsetNext;
}
$return->prev = $offsetPrev;
$return->start = ['year' => $yearStart, 'week' => $weekStart];
$return->offset = $offset;
echo json_encode($return);
}
}

View File

@@ -7,11 +7,12 @@
*/
namespace Controller\Api;
use Controller;
use Controller\Ccp\Sso;
use Data\Mapper as Mapper;
use Model;
class System extends \Controller\AccessController {
class System extends Controller\AccessController {
private $mainQuery = "SELECT
map_sys.constellationID `connstallation_id`,
@@ -68,7 +69,6 @@ class System extends \Controller\AccessController {
* @param \Base $f3
*/
function beforeroute(\Base $f3) {
parent::beforeroute($f3);
// set header for all routes
@@ -80,7 +80,6 @@ class System extends \Controller\AccessController {
* @return string
*/
private function _getQuery(){
$query = $this->mainQuery;
$query .= ' ' . $this->whereQuery;
$query .= ' ' . $this->havingQuery;
@@ -419,8 +418,17 @@ class System extends \Controller\AccessController {
foreach($systemIds as $systemId){
$system->getById($systemId);
if( $system->hasAccess($activeCharacter) ){
$system->setActive(false);
$system->save();
// check whether system should be deleted OR set "inactive"
if(
empty($system->alias) &&
empty($system->description)
){
$system->erase();
}else{
// keep data -> set "inactive"
$system->setActive(false);
$system->save();
}
$system->reset();
}
}

View File

@@ -95,6 +95,9 @@ class Controller {
* @param \Base $f3
*/
public function afterroute(\Base $f3){
// store all user activities that are buffered for logging in this request
self::storeActivities();
if($this->getTemplate()){
// Ajax calls don´t need a page render..
// this happens on client side
@@ -779,6 +782,13 @@ class Controller {
return LogController::getLogger($type);
}
/**
* store activity log data to DB
*/
static function storeActivities(){
LogController::instance()->storeActivities();
}
/**
* removes illegal characters from a Hive-key that are not allowed
* @param $key

View File

@@ -7,10 +7,128 @@
*/
namespace controller;
use DB;
class LogController extends \Prefab {
class LogController extends Controller {
/**
* buffered activity log data for this singleton LogController() class
* -> this buffered data can be stored somewhere (e.g. DB) before HTTP response
* -> should be cleared afterwards!
* @var array
*/
protected $activityLogBuffer = [];
/**
* reserve a "new" character activity for logging
* @param $characterId
* @param $mapId
* @param $action
*/
public function bufferActivity($characterId, $mapId, $action){
$characterId = (int)$characterId;
$mapId = (int)$mapId;
if(
$characterId > 0 &&
$mapId > 0
){
$key = $this->getBufferedActivityKey($characterId, $mapId);
if( is_null($key) ){
$activity = [
'characterId' => $characterId,
'mapId' => $mapId,
$action => 1
];
$this->activityLogBuffer[] = $activity;
}else{
$this->activityLogBuffer[$key][$action]++;
}
}
}
/**
* store all buffered activity log data to DB
*/
public function storeActivities(){
if( !empty($this->activityLogBuffer) ){
$db = DB\Database::instance()->getDB('PF');
$quoteStr = function($str) use ($db) {
return $db->quotekey($str);
};
$placeholderStr = function($str){
return ':' . $str;
};
$updateRule = function($str){
return $str . " = " . $str . " + VALUES(" . $str . ")";
};
$year = (int)date('o');
$yearWeek = (int)date('W');
$db->begin();
foreach($this->activityLogBuffer as $activityData){
$activityData['year'] = $year;
$activityData['week'] = $yearWeek;
$columns = array_keys($activityData);
$columnsQuoted = array_map($quoteStr, $columns);
$placeholder = array_map($placeholderStr, $columns);
$args = array_combine($placeholder, $activityData);
// "filter" columns that can be updated
$columnsForUpdate = array_diff($columns, ['year', 'week', 'characterId', 'mapId']);
$updateSql = array_map($updateRule, $columnsForUpdate);
$sql = "INSERT DELAYED INTO
activity_log (" . implode(', ', $columnsQuoted) . ") values(
" . implode(', ', $placeholder) . "
)
ON DUPLICATE KEY UPDATE
updated = NOW(),
" . implode(', ', $updateSql) . "
";
$db->exec($sql, $args);
}
$db->commit();
// clear activity data for this instance
$this->activityLogBuffer = [];
}
}
/**
* get array key from "buffered activity log" array
* @param int $characterId
* @param int $mapId
* @return int|null
*/
private function getBufferedActivityKey($characterId, $mapId){
$activityKey = null;
if(
$characterId > 0 &&
$mapId > 0
){
foreach($this->activityLogBuffer as $key => $activityData){
if(
$activityData['characterId'] === $characterId &&
$activityData['mapId'] === $mapId
){
$activityKey = $key;
break;
}
}
}
return $activityKey;
}
/**
* get Logger instance

View File

@@ -86,6 +86,8 @@ class Setup extends Controller {
'Model\ConnectionModel',
'Model\SystemSignatureModel',
'Model\ActivityLogModel',
'Model\SystemShipKillModel',
'Model\SystemPodKillModel',
'Model\SystemFactionKillModel',

View File

@@ -47,7 +47,7 @@ class CharacterUpdate {
* delete expired character authentication data
* authentication data is used for cookie based login
* >> php index.php "/cron/deleteAuthenticationData"
* @param $f3
* @param \Base $f3
*/
function deleteAuthenticationData($f3){
DB\Database::instance()->getDB('PF');

View File

@@ -35,11 +35,6 @@ class MapUpdate {
TIMESTAMPDIFF(DAY, map.updated, NOW() ) > :lifetime";
$pfDB->exec($sqlDeactivateExpiredMaps, ['lifetime' => $privateMapLifetime]);
$deactivatedMapsCount = $pfDB->count();
// Log ------------------------
$log = new \Log('cron_' . __FUNCTION__ . '.log');
$log->write( sprintf(self::LOG_TEXT_MAPS, __FUNCTION__, $deactivatedMapsCount) );
}
}
@@ -78,13 +73,13 @@ class MapUpdate {
if($signatureExpire > 0){
$pfDB = DB\Database::instance()->getDB('PF');
$sqlDeleteExpiredSignatures = "DELETE `sys` FROM
`system_signature` `sys` INNER JOIN
$sqlDeleteExpiredSignatures = "DELETE `sigs` FROM
`system_signature` `sigs` INNER JOIN
`system` ON
`system`.`id` = `sys`.`systemId`
`system`.`id` = `sigs`.`systemId`
WHERE
`system`.`active` = 0 AND
TIMESTAMPDIFF(SECOND, `sys`.`updated`, NOW() ) > :lifetime
TIMESTAMPDIFF(SECOND, `sigs`.`updated`, NOW() ) > :lifetime
";
$pfDB->exec($sqlDeleteExpiredSignatures, ['lifetime' => $signatureExpire]);

View File

@@ -0,0 +1,46 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 16.10.2016
* Time: 21:31
*/
namespace cron;
use DB;
class StatisticsUpdate {
const LOG_TEXT_STATISTICS = '%s (%d rows)';
/**
* delete old statistics
* -> older than 1 year
* >> php index.php "/cron/deleteStatisticsData"
* @param \Base $f3
*/
function deleteStatisticsData(\Base $f3){
$currentYear = (int)date('o');
$currentWeek = (int)date('W');
$expiredYear = $currentYear - 1;
$pfDB = DB\Database::instance()->getDB('PF');
$queryData = [
'yearWeekEnd' => strval($expiredYear) . str_pad($currentWeek, 2, 0, STR_PAD_LEFT)
];
$sql = "DELETE FROM
activity_log
WHERE
CONCAT(`year`, `week`) < :yearWeekEnd";
$pfDB->exec($sql, $queryData);
$deletedLogsCount = $pfDB->count();
// Log ------------------------
$log = new \Log('cron_' . __FUNCTION__ . '.log');
$log->write( sprintf(self::LOG_TEXT_STATISTICS, __FUNCTION__, $deletedLogsCount) );
}
}

View File

@@ -0,0 +1,150 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 01.10.2016
* Time: 15:11
*/
namespace Model;
use DB\SQL\Schema;
class ActivityLogModel extends BasicModel {
protected $table = 'activity_log';
protected $fieldConf = [
'active' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
],
'characterId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\CharacterModel',
'constraint' => [
[
'table' => 'character',
'on-delete' => 'CASCADE'
]
]
],
'mapId' => [
'type' => Schema::DT_INT,
'index' => true,
'belongs-to-one' => 'Model\MapModel',
'constraint' => [
[
'table' => 'map',
'on-delete' => 'SET NULL' // keep log data on map delete
]
]
],
// system actions -----------------------------------------------------
'systemCreate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
],
'systemUpdate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
],
'systemDelete' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
],
// connection actions -------------------------------------------------
'connectionCreate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
],
'connectionUpdate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
],
'connectionDelete' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
],
// signature actions -------------------------------------------------
'signatureCreate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
],
'signatureUpdate' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
],
'signatureDelete' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => 0,
],
];
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = 0){
$this->addStaticDateFieldConfig();
parent::__construct($db, $table, $fluid, $ttl);
}
/**
* extent the fieldConf Array with static fields for each table
*/
private function addStaticDateFieldConfig(){
if(is_array($this->fieldConf)){
$staticFieldConfig = [
'year' => [
'type' => Schema::DT_SMALLINT,
'nullable' => false,
'default' => date('o'), // 01.01 could be week 53 -> NOT "current" year!
'index' => true
],
'week' => [ // week in year [1-53]
'type' => Schema::DT_TINYINT,
'nullable' => false,
'default' => 1,
'index' => true
],
];
$this->fieldConf = array_merge($staticFieldConfig, $this->fieldConf);
}
}
/**
* overwrites parent
* @param null $db
* @param null $table
* @param null $fields
* @return bool
*/
public static function setup($db=null, $table=null, $fields=null){
$status = parent::setup($db,$table,$fields);
if($status === true){
$status = parent::setMultiColumnIndex(['year', 'week', 'characterId', 'mapId'], true);
if($status === true){
$status = parent::setMultiColumnIndex(['year', 'week', 'characterId']);
}
}
return $status;
}
}

View File

@@ -56,6 +56,14 @@ abstract class BasicModel extends \DB\Cortex {
*/
protected $allowActiveChange = false;
/**
* enables check for $fieldChanges on update/insert
* -> fields that should be checked need an "activity-log" flag
* in $fieldConf config
* @var bool
*/
protected $enableActivityLogging = true;
/**
* getData() cache key prefix
* -> do not change, otherwise cached data is lost
@@ -77,6 +85,12 @@ abstract class BasicModel extends \DB\Cortex {
*/
public static $enableDataImport = false;
/**
* changed fields (columns) on update/insert
* -> e.g. for character "activity logging"
* @var array
*/
protected $fieldChanges = [];
public function __construct($db = NULL, $table = NULL, $fluid = NULL, $ttl = 0){
@@ -157,10 +171,54 @@ abstract class BasicModel extends \DB\Cortex {
if(!$valid){
$this->throwValidationError($key);
}else{
$this->checkFieldForActivityLogging($key, $val);
return parent::set($key, $val);
}
}
/**
* change default "activity logging" status
* -> enable/disable
* @param $status
*/
public function setActivityLogging($status){
$this->enableActivityLogging = (bool) $status;
}
/**
* check column for value changes,
* --> if column is marked for "activity logging"
* @param string $key
* @param mixed $val
*/
protected function checkFieldForActivityLogging($key, $val){
if( $this->enableActivityLogging ){
$fieldConf = $this->fieldConf[$key];
// check for value changes if field has "activity logging" active
if($fieldConf['activity-log'] === true){
if(
is_numeric($val) ||
$fieldConf['type'] === Schema::DT_BOOL
){
$val = (int)$val;
}
if( $fieldConf['type'] === self::DT_JSON){
$currentValue = $this->get($key);
}else{
$currentValue = $this->get($key, true);
}
if($currentValue !== $val){
// field has changed
in_array($key, $this->fieldChanges) ?: $this->fieldChanges[] = $key;
}
}
}
}
/**
* setter for "active" status
* -> default: keep current "active" status
@@ -638,6 +696,18 @@ abstract class BasicModel extends \DB\Cortex {
return ['added' => $addedCount, 'updated' => $updatedCount, 'deleted' => $deletedCount];
}
/**
* buffer a new activity (action) logging
* -> increment buffered counter
* -> log character activity create/update/delete events
* @param int $characterId
* @param int $mapId
* @param string $action
*/
protected function bufferActivity($characterId, $mapId, $action){
Controller\LogController::instance()->bufferActivity($characterId, $mapId, $action);
}
/**
* get the current class name
* -> namespace not included

View File

@@ -18,11 +18,11 @@ class CharacterLogModel extends BasicModel {
/**
* caching for relational data
* -> 10s matches REST API - Expire: Header-Data
* -> 5s matches REST API - Expire: Header-Data
* for "Location" calls
* @var int
*/
protected $rel_ttl = 10;
protected $rel_ttl = 5;
protected $fieldConf = [
'active' => [

View File

@@ -8,8 +8,9 @@
namespace Model;
use Controller\Api\Route;
use DB\SQL\Schema;
use Controller;
use Controller\Api\Route;
class ConnectionModel extends BasicModel{
@@ -42,7 +43,8 @@ class ConnectionModel extends BasicModel{
'table' => 'system',
'on-delete' => 'CASCADE'
]
]
],
'activity-log' => true
],
'target' => [
'type' => Schema::DT_INT,
@@ -53,15 +55,18 @@ class ConnectionModel extends BasicModel{
'table' => 'system',
'on-delete' => 'CASCADE'
]
]
],
'activity-log' => true
],
'scope' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
'default' => '',
'activity-log' => true
],
'type' => [
'type' => self::DT_JSON
'type' => self::DT_JSON,
'activity-log' => true
],
'eolUpdated' => [
'type' => Schema::DT_TIMESTAMP,
@@ -212,6 +217,66 @@ class ConnectionModel extends BasicModel{
return parent::beforeInsertEvent($self, $pkeys);
}
/**
* Event "Hook" function
* return false will stop any further action
* @param self $self
* @param $pkeys
*/
public function afterInsertEvent($self, $pkeys){
parent::afterInsertEvent($self, $pkeys);
$self->logActivity('connectionCreate');
}
/**
* Event "Hook" function
* return false will stop any further action
* @param self $self
* @param $pkeys
*/
public function afterUpdateEvent($self, $pkeys){
parent::afterUpdateEvent($self, $pkeys);
$self->logActivity('connectionUpdate');
}
/**
* Event "Hook" function
* can be overwritten
* @param self $self
* @param $pkeys
*/
public function afterEraseEvent($self, $pkeys){
parent::afterUpdateEvent($self, $pkeys);
$self->logActivity('connectionDelete');
}
/**
* log character activity create/update/delete events
* @param string $action
*/
protected function logActivity($action){
if(
$this->enableActivityLogging &&
(
$action === 'connectionDelete' ||
!empty($this->fieldChanges)
) &&
$this->get('mapId')->isActivityLogEnabled()
){
// TODO implement "dependency injection" for active character object...
$controller = new Controller\Controller();
$currentActiveCharacter = $controller->getCharacter();
$characterId = is_null($currentActiveCharacter) ? 0 : $currentActiveCharacter->_id;
$mapId = $this->get('mapId', true);
parent::bufferActivity($characterId, $mapId, $action);
}
}
/**
* save connection and check if obj is valid
* @return ConnectionModel|false

View File

@@ -609,6 +609,31 @@ class MapModel extends BasicModel {
}
}
/**
* check if "activity logging" is enabled for this map type
* @return bool
*/
public function isActivityLogEnabled(){
$f3 = self::getF3();
$activityLogEnabled = false;
if( $this->isAlliance() ){
if( $f3->get('PATHFINDER.MAP.ALLIANCE.ACTIVITY_LOGGING') ){
$activityLogEnabled = true;
}
}elseif( $this->isCorporation() ){
if( $f3->get('PATHFINDER.MAP.CORPORATION.ACTIVITY_LOGGING') ){
$activityLogEnabled = true;
}
}elseif( $this->isPrivate() ){
if( $f3->get('PATHFINDER.MAP.PRIVATE.ACTIVITY_LOGGING') ){
$activityLogEnabled = true;
}
}
return $activityLogEnabled;
}
/**
* checks whether this map is private map
* @return bool

View File

@@ -24,7 +24,8 @@ class SystemModel extends BasicModel {
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 1,
'index' => true
'index' => true,
'activity-log' => true
],
'mapId' => [
'type' => Schema::DT_INT,
@@ -49,7 +50,8 @@ class SystemModel extends BasicModel {
'alias' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
'default' => '',
'activity-log' => true
],
'regionId' => [
'type' => Schema::DT_INT,
@@ -106,12 +108,14 @@ class SystemModel extends BasicModel {
'table' => 'system_status',
'on-delete' => 'CASCADE'
]
]
],
'activity-log' => true
],
'locked' => [
'type' => Schema::DT_BOOL,
'nullable' => false,
'default' => 0
'default' => 0,
'activity-log' => true
],
'rallyUpdated' => [
'type' => Schema::DT_TIMESTAMP,
@@ -125,7 +129,8 @@ class SystemModel extends BasicModel {
'description' => [
'type' => Schema::DT_VARCHAR512,
'nullable' => false,
'default' => ''
'default' => '',
'activity-log' => true
],
'posX' => [
'type' => Schema::DT_INT,
@@ -351,6 +356,18 @@ class SystemModel extends BasicModel {
return $rally;
}
/**
* Event "Hook" function
* return false will stop any further action
* @param self $self
* @param $pkeys
*/
public function afterInsertEvent($self, $pkeys){
parent::afterInsertEvent($self, $pkeys);
$self->logActivity('systemCreate');
}
/**
* Event "Hook" function
* can be overwritten
@@ -360,6 +377,8 @@ class SystemModel extends BasicModel {
* @return bool
*/
public function beforeUpdateEvent($self, $pkeys){
$status = parent::beforeUpdateEvent($self, $pkeys);
if( !$self->isActive()){
// system becomes inactive
// reset "rally point" fields
@@ -372,7 +391,8 @@ class SystemModel extends BasicModel {
$connection->erase();
}
}
return true;
return $status;
}
/**
@@ -382,6 +402,8 @@ class SystemModel extends BasicModel {
* @param $pkeys
*/
public function afterUpdateEvent($self, $pkeys){
parent::afterUpdateEvent($self, $pkeys);
// check if rally point mail should be send
if(
$self->newRallyPointSet &&
@@ -389,6 +411,41 @@ class SystemModel extends BasicModel {
){
$self->sendRallyPointMail();
}
$activity = ($self->isActive()) ? 'systemUpdate' : 'systemDelete';
$self->logActivity($activity);
}
/**
* Event "Hook" function
* can be overwritten
* @param self $self
* @param $pkeys
*/
public function afterEraseEvent($self, $pkeys){
parent::afterUpdateEvent($self, $pkeys);
$self->logActivity('systemDelete');
}
/**
* log character activity create/update/delete events
* @param string $action
*/
protected function logActivity($action){
if(
$this->enableActivityLogging &&
(
$action === 'systemDelete' ||
!empty($this->fieldChanges)
) &&
$this->get('mapId')->isActivityLogEnabled()
){
$characterId = $this->get('updatedCharacterId', true);
$mapId = $this->get('mapId', true);
parent::bufferActivity($characterId, $mapId, $action);
}
}
/**

View File

@@ -37,22 +37,26 @@ class SystemSignatureModel extends BasicModel {
'nullable' => false,
'default' => 0,
'index' => true,
'activity-log' => true
],
'typeId' => [
'type' => Schema::DT_INT,
'nullable' => false,
'default' => 0,
'index' => true,
'activity-log' => true
],
'name' => [
'type' => Schema::DT_VARCHAR128,
'nullable' => false,
'default' => ''
'default' => '',
'activity-log' => true
],
'description' => [
'type' => Schema::DT_VARCHAR512,
'nullable' => false,
'default' => ''
'default' => '',
'activity-log' => true
],
'createdCharacterId' => [
'type' => Schema::DT_INT,
@@ -172,6 +176,68 @@ class SystemSignatureModel extends BasicModel {
}
}
/**
* Event "Hook" function
* return false will stop any further action
* @param self $self
* @param $pkeys
*/
public function afterInsertEvent($self, $pkeys){
parent::afterInsertEvent($self, $pkeys);
$self->logActivity('signatureCreate');
}
/**
* Event "Hook" function
* return false will stop any further action
* @param self $self
* @param $pkeys
*/
public function afterUpdateEvent($self, $pkeys){
parent::afterUpdateEvent($self, $pkeys);
$self->logActivity('signatureUpdate');
}
/**
* Event "Hook" function
* can be overwritten
* @param self $self
* @param $pkeys
*/
public function afterEraseEvent($self, $pkeys){
parent::afterUpdateEvent($self, $pkeys);
$self->logActivity('signatureDelete');
}
/**
* log character activity create/update/delete events
* @param string $action
*/
protected function logActivity($action){
if($this->enableActivityLogging){
/**
* @var $map MapModel
*/
$map = $this->get('systemId')->get('mapId');
if(
(
$action === 'signatureDelete' ||
!empty($this->fieldChanges)
) &&
$map->isActivityLogEnabled()
){
$characterId = $this->get('updatedCharacterId', true);
$mapId = $map->_id;
parent::bufferActivity($characterId, $mapId, $action);
}
}
}
/**
* overwrites parent
* @param null $db

View File

@@ -45,23 +45,32 @@ LOGIN = templates/view/login.html
; MAP =============================================================================================
; Map settings for "private", "corporation" and "alliance" maps
; LIFETIME: Map will be deleted after "X" days, by cronjob
; MAX_COUNT: Users can create/view up to "X" maps of a type
; MAX_SHARED: Max number of shared entities per map
; LIFETIME (days):
; - Map will be deleted after "X" days, by cronjob
; MAX_COUNT:
; - Users can create/view up to "X" maps of a type
; MAX_SHARED:
; - Max number of shared entities per map
; ACTIVITY_LOGGING (0: disable, 1: enable):
; - Whether user activity should be logged for a map type
; - E.g. create/update/delete of systems/connections/signatures
[PATHFINDER.MAP.PRIVATE]
LIFETIME = 14
MAX_COUNT = 3
MAX_SHARED = 10
ACTIVITY_LOGGING = 1
[PATHFINDER.MAP.CORPORATION]
LIFETIME = 99999
MAX_COUNT = 3
MAX_SHARED = 3
ACTIVITY_LOGGING = 1
[PATHFINDER.MAP.ALLIANCE]
LIFETIME = 99999
MAX_COUNT = 3
MAX_SHARED = 2
ACTIVITY_LOGGING = 0
; Route search ====================================================================================
[PATHFINDER.ROUTE]
@@ -108,8 +117,8 @@ EXECUTION_LIMIT = 50
CHARACTER_LOG = 300
; expire time for static system data (seconds) (default: 20d)
CONSTELLATION_SYSTEMS = 1728000
; max expire time. Expired cache files will be deleted by cronjob (seconds) (default: 20d)
EXPIRE_MAX = 1728000
; max expire time. Expired cache files will be deleted by cronjob (seconds) (default: 10d)
EXPIRE_MAX = 864000
; expire time for signatures (inactive systems) (seconds) (default 3d)
EXPIRE_SIGNATURES = 259200

View File

@@ -38,6 +38,7 @@ requirejs.config({
raphael: 'lib/raphael-min', // v2.1.2 Raphaël - required for morris (dependency)
bootbox: 'lib/bootbox.min', // v4.4.0 Bootbox.js - custom dialogs - http://bootboxjs.com
easyPieChart: 'lib/jquery.easypiechart.min', // v2.1.6 Easy Pie Chart - HTML 5 pie charts - http://rendro.github.io/easy-pie-chart
peityInlineChart: 'lib/jquery.peity.min', // v3.2.0 Inline Chart - http://benpickles.github.io/peity/
dragToSelect: 'lib/jquery.dragToSelect', // v1.1 Drag to Select - http://andreaslagerkvist.com/jquery/drag-to-select
hoverIntent: 'lib/jquery.hoverIntent.minified', // v1.8.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
fullScreen: 'lib/jquery.fullscreen.min', // v0.5.0 Full screen mode - https://github.com/private-face/jquery.fullscreen
@@ -123,6 +124,9 @@ requirejs.config({
easyPieChart: {
deps : ['jquery']
},
peityInlineChart: {
deps : ['jquery']
},
dragToSelect: {
deps : ['jquery']
},

View File

@@ -35,7 +35,6 @@ define(['jquery'], function($) {
getSystemGraphData: 'api/system/graphData', // ajax URL - get all system graph data
getConstellationData: 'api/system/constellationData', // ajax URL - get system constellation data
setDestination: 'api/system/setDestination', // ajax URL - set destination
// connection API
saveConnection: 'api/connection/save', // ajax URL - save new connection to map
deleteConnection: 'api/connection/delete', // ajax URL - delete connection from map
@@ -45,6 +44,8 @@ define(['jquery'], function($) {
deleteSignatureData: 'api/signature/delete', // ajax URL - delete signature data for system
// route API
searchRoute: 'api/route/search', // ajax URL - search system routes
// stats API
getStatisticsData: 'api/statistic/getData', // ajax URL - get statistics data (activity log)
// GitHub API
gitHubReleases: 'api/github/releases' // ajax URL - get release info from GitHub
},
@@ -52,6 +53,12 @@ define(['jquery'], function($) {
ccpImageServer: 'https://image.eveonline.com/', // CCP image Server
zKillboard: 'https://zkillboard.com/api/' // killboard api
},
breakpoints: [
{ name: 'desktop', width: Infinity },
{ name: 'tablet', width: 1200 },
{ name: 'fablet', width: 780 },
{ name: 'phone', width: 480 }
],
animationSpeed: {
splashOverlay: 300, // "splash" loading overlay
headerLink: 100, // links in head bar

View File

@@ -105,6 +105,14 @@ define([
return '';
};
/**
* set link observer for "version info" dialog
*/
var setVersionLinkObserver = function(){
$('.' + config.navigationVersionLinkClass).off('click').on('click', function(e){
$.fn.releasesDialog();
});
};
/**
* set page observer
@@ -121,11 +129,6 @@ define([
setCookie('cookie', 1, 365);
});
// releases -----------------------------------------------------------
$('.' + config.navigationVersionLinkClass).on('click', function(e){
$.fn.releasesDialog();
});
// manual -------------------------------------------------------------
$('.' + config.navigationLinkManualClass).on('click', function(e){
e.preventDefault();
@@ -138,6 +141,9 @@ define([
$.fn.showCreditsDialog(false, true);
});
// releases -----------------------------------------------------------
setVersionLinkObserver();
// tooltips -----------------------------------------------------------
var mapTooltipOptions = {
toggle: 'tooltip',
@@ -329,6 +335,9 @@ define([
}
};
/**
* init "YouTube" video preview
*/
var initYoutube = function(){
$('.youtube').each(function() {
@@ -468,6 +477,8 @@ define([
notificationPanel.velocity('transition.slideUpIn', {
duration: 300,
complete: function(){
setVersionLinkObserver();
// mark panel as "shown"
Util.getLocalStorage().setItem(storageKey, currentVersion);
}
@@ -565,75 +576,71 @@ define([
}
});
};
// --------------------------------------------------------------------
// request character data for each character panel
$('.' + config.characterSelectionClass + ' .pf-dynamic-area').each(function(){
var characterElement = $(this);
requirejs(['text!templates/ui/character_panel.html', 'mustache'], function(template, Mustache){
characterElement.showLoadingAnimation();
$('.' + config.characterSelectionClass + ' .pf-dynamic-area').each(function(){
var characterElement = $(this);
var requestData = {
cookie: characterElement.data('cookie')
};
characterElement.showLoadingAnimation();
$.ajax({
type: 'POST',
url: Init.path.getCookieCharacterData,
data: requestData,
dataType: 'json',
context: {
href: characterElement.data('href'),
cookieName: requestData.cookie,
characterElement: characterElement
}
}).done(function(responseData, textStatus, request){
var characterElement = this.characterElement;
characterElement.hideLoadingAnimation();
var requestData = {
cookie: characterElement.data('cookie')
};
if(
responseData.error &&
responseData.error.length > 0
){
$('.' + config.dynamicMessageContainerClass).showMessage({
type: responseData.error[0].type,
title: 'Character verification failed',
text: responseData.error[0].message
});
}
$.ajax({
type: 'POST',
url: Init.path.getCookieCharacterData,
data: requestData,
dataType: 'json',
context: {
cookieName: requestData.cookie,
characterElement: characterElement
}
}).done(function(responseData, textStatus, request){
this.characterElement.hideLoadingAnimation();
if(responseData.hasOwnProperty('character')){
if(
responseData.error &&
responseData.error.length > 0
){
$('.' + config.dynamicMessageContainerClass).showMessage({
type: responseData.error[0].type,
title: 'Character verification failed',
text: responseData.error[0].message
});
}
var data = {
link: this.href,
cookieName: this.cookieName,
character: responseData.character
};
if(responseData.hasOwnProperty('character')){
var data = {
link: this.characterElement.data('href'),
cookieName: this.cookieName,
character: responseData.character
};
requirejs(['text!templates/ui/character_panel.html', 'mustache'], function(template, Mustache) {
var content = Mustache.render(template, data);
characterElement.html(content);
this.characterElement.html(content);
// show character panel (animation settings)
initCharacterAnimation(characterElement.find('.' + config.characterImageWrapperClass));
});
}else{
initCharacterAnimation(this.characterElement.find('.' + config.characterImageWrapperClass));
}else{
// character data not available -> remove panel
removeCharacterPanel(this.characterElement);
}
}).fail(function( jqXHR, status, error) {
var characterElement = this.characterElement;
characterElement.hideLoadingAnimation();
// character data not available -> remove panel
removeCharacterPanel(this.characterElement);
}
}).fail(function( jqXHR, status, error) {
var characterElement = this.characterElement;
characterElement.hideLoadingAnimation();
// character data not available -> remove panel
removeCharacterPanel(this.characterElement);
});
});
});
};
/**
* default ajax error handler
* -> show user notifications

View File

@@ -1415,7 +1415,7 @@ define([
{subIcon: '', subAction: 'filter_jumpbridge', subText: 'jumpbridge'}
]},
{divider: true, action: 'delete_systems'},
{icon: 'fa-eraser', action: 'delete_systems', text: 'delete systems'}
{icon: 'fa-trash', action: 'delete_systems', text: 'delete systems'}
]
};
@@ -1452,7 +1452,7 @@ define([
]},
{divider: true, action: 'delete_connection'},
{icon: 'fa-eraser', action: 'delete_connection', text: 'delete'}
{icon: 'fa-trash', action: 'delete_connection', text: 'delete'}
]
};
@@ -1495,7 +1495,7 @@ define([
{subIcon: 'fa-step-forward', subAction: 'add_last_waypoint', subText: 'add new [end]'}
]},
{divider: true, action: 'delete_system'},
{icon: 'fa-eraser', action: 'delete_system', text: 'delete system(s)'}
{icon: 'fa-trash', action: 'delete_system', text: 'delete system(s)'}
]
};

View File

@@ -62,6 +62,7 @@ define([
Init.maxSharedCount = initData.maxSharedCount;
Init.routes = initData.routes;
Init.notificationStatus = initData.notificationStatus;
Init.activityLogging = initData.activityLogging;
// init tab change observer, Once the timers are available
Page.initTabChangeObserver();

View File

@@ -12,6 +12,7 @@ define([
'text!templates/modules/header.html',
'text!templates/modules/footer.html',
'dialog/notification',
'dialog/stats',
'dialog/map_info',
'dialog/account_settings',
'dialog/manual',
@@ -115,6 +116,22 @@ define([
setDocumentObserver();
};
/**
* get main menu title element
* @param title
* @returns {JQuery|*|jQuery}
*/
var getMenuHeadline = function(title){
return $('<div>', {
class: 'panel-heading'
}).prepend(
$('<h2>',{
class: 'panel-title',
text: title
})
);
};
/**
* load left menu content options
*/
@@ -132,20 +149,27 @@ define([
class: 'fa fa-home fa-fw'
})
)
).append(
getMenuHeadline('Information')
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Settings').prepend(
}).html('&nbsp;&nbsp;Statistics').prepend(
$('<i>',{
class: 'fa fa-sliders fa-fw'
class: 'fa fa-line-chart fa-fw'
})
).append(
$('<span>',{
class: 'badge bg-color bg-color-gray txt-color txt-color-warning',
text: 'beta'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowSettingsDialog');
$(document).triggerMenuEvent('ShowStatsDialog');
})
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Effect info').prepend(
$('<i>',{
@@ -156,7 +180,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Jump info').prepend(
$('<i>',{
@@ -165,6 +189,19 @@ define([
).on('click', function(){
$(document).triggerMenuEvent('ShowJumpInfo');
})
).append(
getMenuHeadline('Settings')
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Account').prepend(
$('<i>',{
class: 'fa fa-sliders fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowSettingsDialog');
})
).append(
$('<a>', {
class: 'list-group-item hide', // trigger by js
@@ -197,9 +234,11 @@ define([
).on('click', function(){
$(document).triggerMenuEvent('NotificationTest');
})
).append(
getMenuHeadline('Danger zone')
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-danger',
href: '#'
}).html('&nbsp;&nbsp;Delete account').prepend(
$('<i>',{
@@ -210,7 +249,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-warning',
href: '#'
}).html('&nbsp;&nbsp;Logout').prepend(
$('<i>',{
@@ -240,24 +279,26 @@ define([
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Info').prepend(
$('<i>',{
class: 'fa fa-info fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
}).html('&nbsp;&nbsp;Status').prepend(
$('<i>',{
class: 'fa fa-info fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
})
).append(
getMenuHeadline('Settings')
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Settings').prepend(
}).html('&nbsp;&nbsp;Map config').prepend(
$('<i>',{
class: 'fa fa-sliders fa-fw'
class: 'fa fa-gears fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowMapSettings', {tab: 'settings'});
})
$(document).triggerMenuEvent('ShowMapSettings', {tab: 'settings'});
})
).append(
$('<a>', {
class: 'list-group-item',
@@ -289,34 +330,38 @@ define([
});
})
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Task-Manager').prepend(
$('<i>',{
class: 'fa fa-tasks fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowTaskManager');
})
getMenuHeadline('Help')
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Manual').prepend(
$('<i>',{
class: 'fa fa-info fa-fw'
class: 'fa fa-book fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('Manual');
})
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Delete').prepend(
}).html('&nbsp;&nbsp;Task-Manager').prepend(
$('<i>',{
class: 'fa fa-eraser fa-fw'
class: 'fa fa-tasks fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowTaskManager');
})
).append(
getMenuHeadline('Danger zone')
).append(
$('<a>', {
class: 'list-group-item list-group-item-danger',
href: '#'
}).html('&nbsp;&nbsp;Delete map').prepend(
$('<i>',{
class: 'fa fa-trash fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('DeleteMap');
@@ -494,8 +539,14 @@ define([
}
});
$(document).on('pf:menuShowStatsDialog', function(e){
// show user activity stats dialog
$.fn.showStatsDialog();
return false;
});
$(document).on('pf:menuShowSystemEffectInfo', function(e){
// show system effects info box
// show system effects dialog
$.fn.showSystemEffectInfoDialog();
return false;
});

View File

@@ -295,12 +295,7 @@ define([
order: [[ 9, 'desc' ], [ 3, 'asc' ]],
autoWidth: false,
responsive: {
breakpoints: [
{ name: 'desktop', width: Infinity },
{ name: 'tablet', width: 1200 },
{ name: 'fablet', width: 780 },
{ name: 'phone', width: 480 }
],
breakpoints: Init.breakpoints,
details: false
},
hover: false,

711
js/app/ui/dialog/stats.js Normal file
View File

@@ -0,0 +1,711 @@
/**
* activity stats dialog
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox'
], function($, Init, Util, Render, bootbox, MapUtil) {
'use strict';
var config = {
// dialog
statsDialogId: 'pf-stats-dialog', // id for "stats" dialog
dialogNavigationClass: 'pf-dialog-navigation-list', // class for dialog navigation bar
dialogNavigationListItemClass: 'pf-dialog-navigation-list-item', // class for map manual li main navigation elements
dialogNavigationOffsetClass : 'pf-dialog-navigation-offset', // class for "current" offset filter
dialogNavigationPrevClass : 'pf-dialog-navigation-prev', // class for "prev" period load
dialogNavigationNextClass : 'pf-dialog-navigation-next', // class for "next" period load
// stats/dataTable
statsContainerId: 'pf-stats-dialog-container', // class for statistics container (dynamic ajax content)
statsTableId: 'pf-stats-table', // id for statistics table element
tableImageCellClass: 'pf-table-image-cell', // class for table "image" cells
// charts
statsLineChartClass: 'pf-line-chart' // class for inline chart elements
};
/**
* init blank statistics dataTable
* @param dialogElement
*/
var initStatsTable = function(dialogElement){
var columnNumberWidth = 35;
var lineColor = '#477372';
// render function for inline-chart columns
var renderInlineChartColumn = function(data, type, row, meta){
/*
switch(data.type){
case 'C': lineColor = '#5cb85c'; break;
case 'U': lineColor = '#e28a0d'; break;
case 'D': lineColor = '#a52521'; break;
}*/
if( /^\d+$/.test(data.data) ){
// single digit (e.g. single week filter)
return data.data;
}else{
// period -> prepare line chart
return '<span class="' + config.statsLineChartClass + '" data-peity=\'{ "stroke": "' + lineColor + '" }\'>' + data.data + '</span>';
}
};
// render function for numeric columns
var renderNumericColumn = function(data, type, row, meta){
return data.toLocaleString();
};
// get table element
// Due to "complex" table headers, they are already rendered and part of the stats.html file
var table = dialogElement.find('#' + config.statsTableId);
var statsTable = table.DataTable({
pageLength: 30,
lengthMenu: [[10, 20, 30, 50], [10, 20, 30, 50]],
paging: true,
ordering: true,
order: [ 16, 'desc' ],
info: true,
searching: true,
hover: false,
autoWidth: false,
language: {
emptyTable: 'No statistics found',
zeroRecords: 'No characters found',
lengthMenu: 'Show _MENU_ characters',
info: 'Showing _START_ to _END_ of _TOTAL_ characters'
},
columnDefs: [
{
targets: 0,
title: '<i class="fa fa-hashtag"></i>',
orderable: false,
searchable: false,
width: 10,
class: 'text-right',
data: 'character.id'
},{
targets: 1,
title: '',
orderable: false,
searchable: false,
width: 26,
className: ['text-center', config.tableImageCellClass].join(' '),
data: 'character',
render: {
_: function(data, type, row, meta){
return '<img src="' + Init.url.ccpImageServer + 'Character/' + data.id + '_32.jpg" />';
}
}
},{
targets: 2,
title: 'name',
width: 200,
data: 'character',
render: {
_: 'name',
sort: 'name'
}
},{
targets: 3,
title: 'last login',
searchable: false,
width: 70,
className: ['text-right', 'separator-right'].join(' '),
data: 'character',
render: {
_: 'lastLogin',
sort: 'lastLogin'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter();
}
},{
targets: 4,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 5,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 6,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 7,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'hidden-xs', 'hidden-sm', 'separator-right'].join(' '),
data: 'systemSum',
render: {
_: renderNumericColumn
}
},{
targets: 8,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 9,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 10,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 11,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'hidden-xs', 'hidden-sm', 'separator-right'].join(' '),
data: 'connectionSum',
render: {
_: renderNumericColumn
}
},{
targets: 12,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 13,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 14,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 15,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'hidden-xs', 'hidden-sm', 'separator-right'].join(' '),
data: 'signatureSum',
render: {
_: renderNumericColumn
}
},{
targets: 16,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: 'text-right',
data: 'totalSum',
render: {
_: renderNumericColumn
}
}
],
initComplete: function(settings){
var tableApi = this.api();
// initial statistics data request
var requestData = getRequestDataFromTabPanels(dialogElement);
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
},
drawCallback: function(settings){
this.api().rows().nodes().to$().each(function(i, row){
$(row).find('.' + config.statsLineChartClass).peity('line', {
fill: 'transparent',
height: 18,
min: 0,
width: 50
});
});
},
footerCallback: function ( row, data, start, end, display ) {
var api = this.api();
var sumColumnIndexes = [7, 11, 15, 16];
// column data for "sum" columns over this page
var pageTotalColumns = api
.columns( sumColumnIndexes, { page: 'current'} )
.data();
// sum columns for "total" sum
pageTotalColumns.each(function(colData, index){
pageTotalColumns[index] = colData.reduce(function(a, b){
return a + b;
}, 0);
});
$(sumColumnIndexes).each(function(index, value){
$( api.column( value ).footer() ).text( renderNumericColumn(pageTotalColumns[index]) );
});
},
data: [] // will be added dynamic
});
statsTable.on('order.dt search.dt', function(){
statsTable.column(0, {search:'applied', order:'applied'}).nodes().each(function(cell, i){
$(cell).html( (i + 1) + '.&nbsp;&nbsp;');
});
}).draw();
var tooltipElements = dialogElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip();
};
/**
* request raw statistics data and execute callback
* @param requestData
* @param context
*/
var getStatsData = function(requestData, context){
context.dynamicArea = $('#' + config.statsContainerId + ' .pf-dynamic-area');
context.dynamicArea.showLoadingAnimation();
$.ajax({
type: 'POST',
url: Init.path.getStatisticsData,
data: requestData,
dataType: 'json',
context: context
}).done(function(data){
this.dynamicArea.hideLoadingAnimation();
this.callback(data);
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': loadStatistics', text: reason, type: 'warning'});
});
};
/**
* update dataTable with response data
* update "header"/"filter" elements in dialog
* @param responseData
*/
var drawStatsTable = function(responseData){
var dialogElement = $('#' + config.statsDialogId);
// update filter/header -----------------------------------------------------------------------------
var navigationListElements = $('.' + config.dialogNavigationClass);
navigationListElements.find('a[data-type="typeId"][data-value="' + responseData.typeId + '"]').tab('show');
navigationListElements.find('a[data-type="period"][data-value="' + responseData.period + '"]').tab('show');
// update period pagination -------------------------------------------------------------------------
var prevButton = dialogElement.find('.' + config.dialogNavigationPrevClass);
prevButton.data('newOffset', responseData.prev);
prevButton.find('span').text('Week ' + responseData.prev.week + ', ' + responseData.prev.year);
prevButton.css('visibility', 'visible');
var nextButton = dialogElement.find('.' + config.dialogNavigationNextClass);
if(responseData.next){
nextButton.data('newOffset', responseData.next);
nextButton.find('span').text('Week ' + responseData.next.week + ', ' + responseData.next.year);
nextButton.css('visibility', 'visible');
}else{
nextButton.css('visibility', 'hidden');
}
// update current period information label ----------------------------------------------------------
// if period == "weekly" there is no "offset" -> just a single week
var offsetText = 'Week ' + responseData.start.week + ', ' + responseData.start.year;
if(responseData.period !== 'weekly'){
offsetText += ' <small><i class="fa fa-fw fa-minus"></i></small> ' +
'Week ' + responseData.offset.week + ', ' + responseData.offset.year;
}
dialogElement.find('.' + config.dialogNavigationOffsetClass)
.data('start', responseData.start)
.data('period', responseData.period)
.html(offsetText);
// clear and (re)-fill table ------------------------------------------------------------------------
var formattedData = formatStatisticsData(responseData);
this.tableApi.clear();
this.tableApi.rows.add(formattedData).draw();
};
/**
* format statistics data for dataTable
* -> e.g. format inline-chart data
* @param statsData
* @returns {Array}
*/
var formatStatisticsData = function(statsData){
var formattedData = [];
var yearStart = statsData.start.year;
var weekStart = statsData.start.week;
var weekCount = statsData.weekCount;
var yearWeeks = statsData.yearWeeks;
var tempRand = function(min, max){
return Math.random() * (max - min) + min;
};
// format/sum week statistics data for inline charts
var formatWeekData = function(weeksData){
var currentYear = yearStart;
var currentWeek = weekStart;
var formattedWeeksData = {
systemCreate: [],
systemUpdate: [],
systemDelete: [],
connectionCreate: [],
connectionUpdate: [],
connectionDelete: [],
signatureCreate: [],
signatureUpdate: [],
signatureDelete: [],
systemSum: 0,
connectionSum: 0,
signatureSum: 0
};
for(let i = 0; i < weekCount; i++){
let yearWeekProp = currentYear + '' + currentWeek;
if(weeksData.hasOwnProperty( yearWeekProp )){
let weekData = weeksData[ yearWeekProp ];
// system -------------------------------------------------------------------------------
formattedWeeksData.systemCreate.push( weekData.systemCreate );
formattedWeeksData.systemSum += parseInt( weekData.systemCreate );
formattedWeeksData.systemUpdate.push( weekData.systemUpdate );
formattedWeeksData.systemSum += parseInt( weekData.systemUpdate );
formattedWeeksData.systemDelete.push( weekData.systemDelete );
formattedWeeksData.systemSum += parseInt( weekData.systemDelete );
// connection ---------------------------------------------------------------------------
formattedWeeksData.connectionCreate.push( weekData.connectionCreate );
formattedWeeksData.connectionSum += parseInt( weekData.connectionCreate );
formattedWeeksData.connectionUpdate.push( weekData.connectionUpdate );
formattedWeeksData.connectionSum += parseInt( weekData.connectionUpdate );
formattedWeeksData.connectionDelete.push( weekData.connectionDelete );
formattedWeeksData.connectionSum += parseInt( weekData.connectionDelete );
// signature ----------------------------------------------------------------------------
formattedWeeksData.signatureCreate.push( weekData.signatureCreate );
formattedWeeksData.signatureSum += parseInt( weekData.signatureCreate );
formattedWeeksData.signatureUpdate.push( weekData.signatureUpdate );
formattedWeeksData.signatureSum += parseInt( weekData.signatureUpdate );
formattedWeeksData.signatureDelete.push( weekData.signatureDelete );
formattedWeeksData.signatureSum += parseInt( weekData.signatureDelete );
}else{
// system -------------------------------------------------------------------------------
formattedWeeksData.systemCreate.push(0);
formattedWeeksData.systemUpdate.push(0);
formattedWeeksData.systemDelete.push(0);
// connection ---------------------------------------------------------------------------
formattedWeeksData.connectionCreate.push(0);
formattedWeeksData.connectionUpdate.push(0);
formattedWeeksData.connectionDelete.push(0);
// signature ----------------------------------------------------------------------------
formattedWeeksData.signatureCreate.push(0);
formattedWeeksData.signatureUpdate.push(0);
formattedWeeksData.signatureDelete.push(0);
}
currentWeek++;
if( currentWeek > yearWeeks[currentYear] ){
currentWeek = 1;
currentYear++;
}
}
// system ---------------------------------------------------------------------------------------
formattedWeeksData.systemCreate = formattedWeeksData.systemCreate.join(',');
formattedWeeksData.systemUpdate = formattedWeeksData.systemUpdate.join(',');
formattedWeeksData.systemDelete = formattedWeeksData.systemDelete.join(',');
// connection -----------------------------------------------------------------------------------
formattedWeeksData.connectionCreate = formattedWeeksData.connectionCreate.join(',');
formattedWeeksData.connectionUpdate = formattedWeeksData.connectionUpdate.join(',');
formattedWeeksData.connectionDelete = formattedWeeksData.connectionDelete.join(',');
// signature ------------------------------------------------------------------------------------
formattedWeeksData.signatureCreate = formattedWeeksData.signatureCreate.join(',');
formattedWeeksData.signatureUpdate = formattedWeeksData.signatureUpdate.join(',');
formattedWeeksData.signatureDelete = formattedWeeksData.signatureDelete.join(',');
return formattedWeeksData;
};
$.each(statsData.statistics, function(characterId, data){
var formattedWeeksData = formatWeekData(data.weeks);
var rowData = {
character: {
id: characterId,
name: data.name,
lastLogin: data.lastLogin
},
systemCreate: {
type: 'C',
data: formattedWeeksData.systemCreate
},
systemUpdate: {
type: 'U',
data: formattedWeeksData.systemUpdate
},
systemDelete: {
type: 'D',
data: formattedWeeksData.systemDelete
},
systemSum: formattedWeeksData.systemSum,
connectionCreate: {
type: 'C',
data: formattedWeeksData.connectionCreate
},
connectionUpdate: {
type: 'U',
data: formattedWeeksData.connectionUpdate
},
connectionDelete: {
type: 'D',
data: formattedWeeksData.connectionDelete
},
connectionSum: formattedWeeksData.connectionSum,
signatureCreate: {
type: 'C',
data: formattedWeeksData.signatureCreate
},
signatureUpdate: {
type: 'U',
data: formattedWeeksData.signatureUpdate
},
signatureDelete: {
type: 'D',
data: formattedWeeksData.signatureDelete
},
signatureSum: formattedWeeksData.signatureSum,
totalSum: formattedWeeksData.systemSum + formattedWeeksData.connectionSum + formattedWeeksData.signatureSum
};
formattedData.push(rowData);
});
return formattedData;
};
/**
*
* @param dialogElement
* @returns {{}}
*/
var getRequestDataFromTabPanels = function(dialogElement){
var requestData = {};
// get data from "tab" panel links ------------------------------------------------------------------
var navigationListElements = dialogElement.find('.' + config.dialogNavigationClass);
navigationListElements.find('.' + config.dialogNavigationListItemClass + '.active a').each(function(){
var linkElement = $(this);
requestData[linkElement.data('type')]= linkElement.data('value');
});
// get current period (no offset) data (if available) -----------------------------------------------
var navigationOffsetElement = dialogElement.find('.' + config.dialogNavigationOffsetClass);
var startData = navigationOffsetElement.data('start');
var periodOld = navigationOffsetElement.data('period');
// if period switch was detected
// -> "year" and "week" should not be send
// -> start from "now"
if(
requestData.period === periodOld &&
startData
){
requestData.year = startData.year;
requestData.week = startData.week;
}
return requestData;
};
/**
* check if "activity log" type is enabled for a group
* @param type
* @returns {boolean}
*/
var isTabTypeEnabled = function(type){
var enabled = false;
switch(type){
case 'private':
if(Init.activityLogging.character){
enabled = true;
}
break;
case 'corporation':
if(
Init.activityLogging.corporation &&
Util.getCurrentUserInfo('corporationId')
){
enabled = true;
}
break;
case 'alliance':
if(
Init.activityLogging.alliance &&
Util.getCurrentUserInfo('allianceId')
){
enabled = true;
}
break;
}
return enabled;
};
/**
* show activity stats dialog
*/
$.fn.showStatsDialog = function(){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'peityInlineChart'], function(template, Mustache) {
var data = {
id: config.statsDialogId,
dialogNavigationClass: config.dialogNavigationClass,
dialogNavLiClass: config.dialogNavigationListItemClass,
enablePrivateTab: isTabTypeEnabled('private'),
enableCorporationTab: isTabTypeEnabled('corporation'),
enableAllianceTab: isTabTypeEnabled('alliance'),
statsContainerId: config.statsContainerId,
statsTableId: config.statsTableId,
dialogNavigationOffsetClass: config.dialogNavigationOffsetClass,
dialogNavigationPrevClass: config.dialogNavigationPrevClass,
dialogNavigationNextClass: config.dialogNavigationNextClass
};
var content = Mustache.render(template, data);
var statsDialog = bootbox.dialog({
title: 'Statistics',
message: content,
size: 'large',
show: false,
buttons: {
close: {
label: 'close',
className: 'btn-default'
}
}
});
// model events
statsDialog.on('show.bs.modal', function(e) {
var dialogElement = $(e.target);
initStatsTable(dialogElement);
});
// Tab module events
statsDialog.find('a[data-toggle="tab"]').on('show.bs.tab', function (e, b, c) {
if( $(e.target).parent().hasClass('disabled') ){
// no action on "disabled" tabs
return false;
}
});
statsDialog.find('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var requestData = getRequestDataFromTabPanels(statsDialog);
var tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
});
// offset change links
statsDialog.find('.' + config.dialogNavigationPrevClass + ', .' + config.dialogNavigationNextClass).on('click', function(){
var offsetData = $(this).data('newOffset');
if(offsetData){
// this should NEVER fail!
// get "base" request data (e.g. typeId, period)
// --> overwrite period data with new period data
var tmpRequestData = getRequestDataFromTabPanels(statsDialog);
var requestData = $.extend({}, tmpRequestData, offsetData);
var tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
}
});
// show dialog
statsDialog.modal('show');
});
};
});

View File

@@ -316,7 +316,7 @@ define([
var settingsDialog = bootbox.dialog({
title: 'Route settings',
message: content,
show: false,
show: false,
buttons: {
close: {
label: 'cancel',
@@ -444,7 +444,6 @@ define([
data: requestData,
context: context
}).done(function(routesData){
this.moduleElement.hideLoadingAnimation();
// execute callback
@@ -886,7 +885,14 @@ define([
};
/**
* draw route table
* @param mapId
* @param moduleElement
* @param systemFromData
* @param routesTable
* @param systemsTo
*/
var drawRouteTable = function(mapId, moduleElement, systemFromData, routesTable, systemsTo){
var requestRouteData = [];
var currentTimestamp = Util.getServerTime().getTime();

View File

@@ -121,7 +121,9 @@ define([
var loadingElement = $(this);
var overlay = loadingElement.find('.' + config.ajaxOverlayClass );
$(overlay).velocity('reverse', {
// important: "stop" is required to stop "show" animation
// -> otherwise "complete" callback is not fired!
$(overlay).velocity('stop').velocity('reverse', {
complete: function(){
$(this).remove();
// enable all events

13
js/lib/jquery.peity.min.js vendored Normal file
View File

@@ -0,0 +1,13 @@
// Peity jQuery plugin version 3.2.0
// (c) 2015 Ben Pickles
//
// http://benpickles.github.io/peity
//
// Released under MIT license.
(function(k,w,h,v){var d=k.fn.peity=function(a,b){y&&this.each(function(){var e=k(this),c=e.data("_peity");c?(a&&(c.type=a),k.extend(c.opts,b)):(c=new x(e,a,k.extend({},d.defaults[a],e.data("peity"),b)),e.change(function(){c.draw()}).data("_peity",c));c.draw()});return this},x=function(a,b,e){this.$el=a;this.type=b;this.opts=e},o=x.prototype,q=o.svgElement=function(a,b){return k(w.createElementNS("http://www.w3.org/2000/svg",a)).attr(b)},y="createElementNS"in w&&q("svg",{})[0].createSVGRect;o.draw=
function(){var a=this.opts;d.graphers[this.type].call(this,a);a.after&&a.after.call(this,a)};o.fill=function(){var a=this.opts.fill;return k.isFunction(a)?a:function(b,e){return a[e%a.length]}};o.prepare=function(a,b){this.$svg||this.$el.hide().after(this.$svg=q("svg",{"class":"peity"}));return this.$svg.empty().data("peity",this).attr({height:b,width:a})};o.values=function(){return k.map(this.$el.text().split(this.opts.delimiter),function(a){return parseFloat(a)})};d.defaults={};d.graphers={};d.register=
function(a,b,e){this.defaults[a]=b;this.graphers[a]=e};d.register("pie",{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(a){if(!a.delimiter){var b=this.$el.text().match(/[^0-9\.]/);a.delimiter=b?b[0]:","}b=k.map(this.values(),function(a){return 0<a?a:0});if("/"==a.delimiter)var e=b[0],b=[e,h.max(0,b[1]-e)];for(var c=0,e=b.length,t=0;c<e;c++)t+=b[c];t||(e=2,t=1,b=[0,1]);var l=2*a.radius,l=this.prepare(a.width||l,a.height||l),c=l.width(),f=l.height(),j=c/2,d=f/2,f=h.min(j,d),a=a.innerRadius;
"donut"==this.type&&!a&&(a=0.5*f);for(var r=h.PI,s=this.fill(),g=this.scale=function(a,b){var c=a/t*r*2-r/2;return[b*h.cos(c)+j,b*h.sin(c)+d]},m=0,c=0;c<e;c++){var u=b[c],i=u/t;if(0!=i){if(1==i)if(a)var i=j-0.01,p=d-f,n=d-a,i=q("path",{d:["M",j,p,"A",f,f,0,1,1,i,p,"L",i,n,"A",a,a,0,1,0,j,n].join(" ")});else i=q("circle",{cx:j,cy:d,r:f});else p=m+u,n=["M"].concat(g(m,f),"A",f,f,0,0.5<i?1:0,1,g(p,f),"L"),a?n=n.concat(g(p,a),"A",a,a,0,0.5<i?1:0,0,g(m,a)):n.push(j,d),m+=u,i=q("path",{d:n.join(" ")});
i.attr("fill",s.call(this,u,c,b));l.append(i)}}});d.register("donut",k.extend(!0,{},d.defaults.pie),function(a){d.graphers.pie.call(this,a)});d.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(a){var b=this.values();1==b.length&&b.push(b[0]);for(var e=h.max.apply(h,a.max==v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=a.strokeWidth,f=d.width(),j=d.height()-l,k=e-c,e=this.x=function(a){return a*
(f/(b.length-1))},r=this.y=function(a){var b=j;k&&(b-=(a-c)/k*j);return b+l/2},s=r(h.max(c,0)),g=[0,s],m=0;m<b.length;m++)g.push(e(m),r(b[m]));g.push(f,s);a.fill&&d.append(q("polygon",{fill:a.fill,points:g.join(" ")}));l&&d.append(q("polyline",{fill:"none",points:g.slice(2,g.length-2).join(" "),stroke:a.stroke,"stroke-width":l,"stroke-linecap":"square"}))});d.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:0.1,width:32},function(a){for(var b=this.values(),e=h.max.apply(h,a.max==
v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=d.width(),f=d.height(),j=e-c,a=a.padding,k=this.fill(),r=this.x=function(a){return a*l/b.length},s=this.y=function(a){return f-(j?(a-c)/j*f:1)},g=0;g<b.length;g++){var m=r(g+a),u=r(g+1-a)-m,i=b[g],p=s(i),n=p,o;j?0>i?n=s(h.min(e,0)):p=s(h.max(c,0)):o=1;o=p-n;0==o&&(o=1,0<e&&j&&n--);d.append(q("rect",{fill:k.call(this,i,g,b),x:m,y:n,width:u,height:o}))}})})(jQuery,document,Math);

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@@ -38,6 +38,7 @@ requirejs.config({
raphael: 'lib/raphael-min', // v2.1.2 Raphaël - required for morris (dependency)
bootbox: 'lib/bootbox.min', // v4.4.0 Bootbox.js - custom dialogs - http://bootboxjs.com
easyPieChart: 'lib/jquery.easypiechart.min', // v2.1.6 Easy Pie Chart - HTML 5 pie charts - http://rendro.github.io/easy-pie-chart
peityInlineChart: 'lib/jquery.peity.min', // v3.2.0 Inline Chart - http://benpickles.github.io/peity/
dragToSelect: 'lib/jquery.dragToSelect', // v1.1 Drag to Select - http://andreaslagerkvist.com/jquery/drag-to-select
hoverIntent: 'lib/jquery.hoverIntent.minified', // v1.8.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html
fullScreen: 'lib/jquery.fullscreen.min', // v0.5.0 Full screen mode - https://github.com/private-face/jquery.fullscreen
@@ -123,6 +124,9 @@ requirejs.config({
easyPieChart: {
deps : ['jquery']
},
peityInlineChart: {
deps : ['jquery']
},
dragToSelect: {
deps : ['jquery']
},

View File

@@ -35,7 +35,6 @@ define(['jquery'], function($) {
getSystemGraphData: 'api/system/graphData', // ajax URL - get all system graph data
getConstellationData: 'api/system/constellationData', // ajax URL - get system constellation data
setDestination: 'api/system/setDestination', // ajax URL - set destination
// connection API
saveConnection: 'api/connection/save', // ajax URL - save new connection to map
deleteConnection: 'api/connection/delete', // ajax URL - delete connection from map
@@ -45,6 +44,8 @@ define(['jquery'], function($) {
deleteSignatureData: 'api/signature/delete', // ajax URL - delete signature data for system
// route API
searchRoute: 'api/route/search', // ajax URL - search system routes
// stats API
getStatisticsData: 'api/statistic/getData', // ajax URL - get statistics data (activity log)
// GitHub API
gitHubReleases: 'api/github/releases' // ajax URL - get release info from GitHub
},
@@ -52,6 +53,12 @@ define(['jquery'], function($) {
ccpImageServer: 'https://image.eveonline.com/', // CCP image Server
zKillboard: 'https://zkillboard.com/api/' // killboard api
},
breakpoints: [
{ name: 'desktop', width: Infinity },
{ name: 'tablet', width: 1200 },
{ name: 'fablet', width: 780 },
{ name: 'phone', width: 480 }
],
animationSpeed: {
splashOverlay: 300, // "splash" loading overlay
headerLink: 100, // links in head bar

View File

@@ -105,6 +105,14 @@ define([
return '';
};
/**
* set link observer for "version info" dialog
*/
var setVersionLinkObserver = function(){
$('.' + config.navigationVersionLinkClass).off('click').on('click', function(e){
$.fn.releasesDialog();
});
};
/**
* set page observer
@@ -121,11 +129,6 @@ define([
setCookie('cookie', 1, 365);
});
// releases -----------------------------------------------------------
$('.' + config.navigationVersionLinkClass).on('click', function(e){
$.fn.releasesDialog();
});
// manual -------------------------------------------------------------
$('.' + config.navigationLinkManualClass).on('click', function(e){
e.preventDefault();
@@ -138,6 +141,9 @@ define([
$.fn.showCreditsDialog(false, true);
});
// releases -----------------------------------------------------------
setVersionLinkObserver();
// tooltips -----------------------------------------------------------
var mapTooltipOptions = {
toggle: 'tooltip',
@@ -329,6 +335,9 @@ define([
}
};
/**
* init "YouTube" video preview
*/
var initYoutube = function(){
$('.youtube').each(function() {
@@ -468,6 +477,8 @@ define([
notificationPanel.velocity('transition.slideUpIn', {
duration: 300,
complete: function(){
setVersionLinkObserver();
// mark panel as "shown"
Util.getLocalStorage().setItem(storageKey, currentVersion);
}

View File

@@ -1415,7 +1415,7 @@ define([
{subIcon: '', subAction: 'filter_jumpbridge', subText: 'jumpbridge'}
]},
{divider: true, action: 'delete_systems'},
{icon: 'fa-eraser', action: 'delete_systems', text: 'delete systems'}
{icon: 'fa-trash', action: 'delete_systems', text: 'delete systems'}
]
};
@@ -1452,7 +1452,7 @@ define([
]},
{divider: true, action: 'delete_connection'},
{icon: 'fa-eraser', action: 'delete_connection', text: 'delete'}
{icon: 'fa-trash', action: 'delete_connection', text: 'delete'}
]
};
@@ -1495,7 +1495,7 @@ define([
{subIcon: 'fa-step-forward', subAction: 'add_last_waypoint', subText: 'add new [end]'}
]},
{divider: true, action: 'delete_system'},
{icon: 'fa-eraser', action: 'delete_system', text: 'delete system(s)'}
{icon: 'fa-trash', action: 'delete_system', text: 'delete system(s)'}
]
};

View File

@@ -62,6 +62,7 @@ define([
Init.maxSharedCount = initData.maxSharedCount;
Init.routes = initData.routes;
Init.notificationStatus = initData.notificationStatus;
Init.activityLogging = initData.activityLogging;
// init tab change observer, Once the timers are available
Page.initTabChangeObserver();

View File

@@ -12,6 +12,7 @@ define([
'text!templates/modules/header.html',
'text!templates/modules/footer.html',
'dialog/notification',
'dialog/stats',
'dialog/map_info',
'dialog/account_settings',
'dialog/manual',
@@ -115,6 +116,22 @@ define([
setDocumentObserver();
};
/**
* get main menu title element
* @param title
* @returns {JQuery|*|jQuery}
*/
var getMenuHeadline = function(title){
return $('<div>', {
class: 'panel-heading'
}).prepend(
$('<h2>',{
class: 'panel-title',
text: title
})
);
};
/**
* load left menu content options
*/
@@ -132,20 +149,27 @@ define([
class: 'fa fa-home fa-fw'
})
)
).append(
getMenuHeadline('Information')
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Settings').prepend(
}).html('&nbsp;&nbsp;Statistics').prepend(
$('<i>',{
class: 'fa fa-sliders fa-fw'
class: 'fa fa-line-chart fa-fw'
})
).append(
$('<span>',{
class: 'badge bg-color bg-color-gray txt-color txt-color-warning',
text: 'beta'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowSettingsDialog');
$(document).triggerMenuEvent('ShowStatsDialog');
})
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Effect info').prepend(
$('<i>',{
@@ -156,7 +180,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Jump info').prepend(
$('<i>',{
@@ -165,6 +189,19 @@ define([
).on('click', function(){
$(document).triggerMenuEvent('ShowJumpInfo');
})
).append(
getMenuHeadline('Settings')
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Account').prepend(
$('<i>',{
class: 'fa fa-sliders fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowSettingsDialog');
})
).append(
$('<a>', {
class: 'list-group-item hide', // trigger by js
@@ -197,9 +234,11 @@ define([
).on('click', function(){
$(document).triggerMenuEvent('NotificationTest');
})
).append(
getMenuHeadline('Danger zone')
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-danger',
href: '#'
}).html('&nbsp;&nbsp;Delete account').prepend(
$('<i>',{
@@ -210,7 +249,7 @@ define([
})
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-warning',
href: '#'
}).html('&nbsp;&nbsp;Logout').prepend(
$('<i>',{
@@ -240,24 +279,26 @@ define([
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Info').prepend(
$('<i>',{
class: 'fa fa-info fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
}).html('&nbsp;&nbsp;Status').prepend(
$('<i>',{
class: 'fa fa-info fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowMapInfo');
})
).append(
getMenuHeadline('Settings')
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Settings').prepend(
}).html('&nbsp;&nbsp;Map config').prepend(
$('<i>',{
class: 'fa fa-sliders fa-fw'
class: 'fa fa-gears fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowMapSettings', {tab: 'settings'});
})
$(document).triggerMenuEvent('ShowMapSettings', {tab: 'settings'});
})
).append(
$('<a>', {
class: 'list-group-item',
@@ -289,34 +330,38 @@ define([
});
})
).append(
$('<a>', {
class: 'list-group-item',
href: '#'
}).html('&nbsp;&nbsp;Task-Manager').prepend(
$('<i>',{
class: 'fa fa-tasks fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowTaskManager');
})
getMenuHeadline('Help')
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Manual').prepend(
$('<i>',{
class: 'fa fa-info fa-fw'
class: 'fa fa-book fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('Manual');
})
).append(
$('<a>', {
class: 'list-group-item',
class: 'list-group-item list-group-item-info',
href: '#'
}).html('&nbsp;&nbsp;Delete').prepend(
}).html('&nbsp;&nbsp;Task-Manager').prepend(
$('<i>',{
class: 'fa fa-eraser fa-fw'
class: 'fa fa-tasks fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('ShowTaskManager');
})
).append(
getMenuHeadline('Danger zone')
).append(
$('<a>', {
class: 'list-group-item list-group-item-danger',
href: '#'
}).html('&nbsp;&nbsp;Delete map').prepend(
$('<i>',{
class: 'fa fa-trash fa-fw'
})
).on('click', function(){
$(document).triggerMenuEvent('DeleteMap');
@@ -494,8 +539,14 @@ define([
}
});
$(document).on('pf:menuShowStatsDialog', function(e){
// show user activity stats dialog
$.fn.showStatsDialog();
return false;
});
$(document).on('pf:menuShowSystemEffectInfo', function(e){
// show system effects info box
// show system effects dialog
$.fn.showSystemEffectInfoDialog();
return false;
});

View File

@@ -295,12 +295,7 @@ define([
order: [[ 9, 'desc' ], [ 3, 'asc' ]],
autoWidth: false,
responsive: {
breakpoints: [
{ name: 'desktop', width: Infinity },
{ name: 'tablet', width: 1200 },
{ name: 'fablet', width: 780 },
{ name: 'phone', width: 480 }
],
breakpoints: Init.breakpoints,
details: false
},
hover: false,

View File

@@ -0,0 +1,711 @@
/**
* activity stats dialog
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox'
], function($, Init, Util, Render, bootbox, MapUtil) {
'use strict';
var config = {
// dialog
statsDialogId: 'pf-stats-dialog', // id for "stats" dialog
dialogNavigationClass: 'pf-dialog-navigation-list', // class for dialog navigation bar
dialogNavigationListItemClass: 'pf-dialog-navigation-list-item', // class for map manual li main navigation elements
dialogNavigationOffsetClass : 'pf-dialog-navigation-offset', // class for "current" offset filter
dialogNavigationPrevClass : 'pf-dialog-navigation-prev', // class for "prev" period load
dialogNavigationNextClass : 'pf-dialog-navigation-next', // class for "next" period load
// stats/dataTable
statsContainerId: 'pf-stats-dialog-container', // class for statistics container (dynamic ajax content)
statsTableId: 'pf-stats-table', // id for statistics table element
tableImageCellClass: 'pf-table-image-cell', // class for table "image" cells
// charts
statsLineChartClass: 'pf-line-chart' // class for inline chart elements
};
/**
* init blank statistics dataTable
* @param dialogElement
*/
var initStatsTable = function(dialogElement){
var columnNumberWidth = 35;
var lineColor = '#477372';
// render function for inline-chart columns
var renderInlineChartColumn = function(data, type, row, meta){
/*
switch(data.type){
case 'C': lineColor = '#5cb85c'; break;
case 'U': lineColor = '#e28a0d'; break;
case 'D': lineColor = '#a52521'; break;
}*/
if( /^\d+$/.test(data.data) ){
// single digit (e.g. single week filter)
return data.data;
}else{
// period -> prepare line chart
return '<span class="' + config.statsLineChartClass + '" data-peity=\'{ "stroke": "' + lineColor + '" }\'>' + data.data + '</span>';
}
};
// render function for numeric columns
var renderNumericColumn = function(data, type, row, meta){
return data.toLocaleString();
};
// get table element
// Due to "complex" table headers, they are already rendered and part of the stats.html file
var table = dialogElement.find('#' + config.statsTableId);
var statsTable = table.DataTable({
pageLength: 30,
lengthMenu: [[10, 20, 30, 50], [10, 20, 30, 50]],
paging: true,
ordering: true,
order: [ 16, 'desc' ],
info: true,
searching: true,
hover: false,
autoWidth: false,
language: {
emptyTable: 'No statistics found',
zeroRecords: 'No characters found',
lengthMenu: 'Show _MENU_ characters',
info: 'Showing _START_ to _END_ of _TOTAL_ characters'
},
columnDefs: [
{
targets: 0,
title: '<i class="fa fa-hashtag"></i>',
orderable: false,
searchable: false,
width: 10,
class: 'text-right',
data: 'character.id'
},{
targets: 1,
title: '',
orderable: false,
searchable: false,
width: 26,
className: ['text-center', config.tableImageCellClass].join(' '),
data: 'character',
render: {
_: function(data, type, row, meta){
return '<img src="' + Init.url.ccpImageServer + 'Character/' + data.id + '_32.jpg" />';
}
}
},{
targets: 2,
title: 'name',
width: 200,
data: 'character',
render: {
_: 'name',
sort: 'name'
}
},{
targets: 3,
title: 'last login',
searchable: false,
width: 70,
className: ['text-right', 'separator-right'].join(' '),
data: 'character',
render: {
_: 'lastLogin',
sort: 'lastLogin'
},
createdCell: function(cell, cellData, rowData, rowIndex, colIndex){
$(cell).initTimestampCounter();
}
},{
targets: 4,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 5,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 6,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'systemDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 7,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'hidden-xs', 'hidden-sm', 'separator-right'].join(' '),
data: 'systemSum',
render: {
_: renderNumericColumn
}
},{
targets: 8,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 9,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 10,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'connectionDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 11,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'hidden-xs', 'hidden-sm', 'separator-right'].join(' '),
data: 'connectionSum',
render: {
_: renderNumericColumn
}
},{
targets: 12,
title: '<span title="created" data-toggle="tooltip">C&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureCreate',
render: {
_: renderInlineChartColumn
}
},{
targets: 13,
title: '<span title="updated" data-toggle="tooltip">U&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureUpdate',
render: {
_: renderInlineChartColumn
}
},{
targets: 14,
title: '<span title="deleted" data-toggle="tooltip">D&nbsp;&nbsp;</span>',
orderable: false,
searchable: false,
width: columnNumberWidth,
className: ['text-right', 'hidden-xs', 'hidden-sm'].join(' '),
data: 'signatureDelete',
render: {
_: renderInlineChartColumn
}
},{
targets: 15,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: ['text-right', 'hidden-xs', 'hidden-sm', 'separator-right'].join(' '),
data: 'signatureSum',
render: {
_: renderNumericColumn
}
},{
targets: 16,
title: 'Σ&nbsp;&nbsp;',
searchable: false,
width: 20,
className: 'text-right',
data: 'totalSum',
render: {
_: renderNumericColumn
}
}
],
initComplete: function(settings){
var tableApi = this.api();
// initial statistics data request
var requestData = getRequestDataFromTabPanels(dialogElement);
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
},
drawCallback: function(settings){
this.api().rows().nodes().to$().each(function(i, row){
$(row).find('.' + config.statsLineChartClass).peity('line', {
fill: 'transparent',
height: 18,
min: 0,
width: 50
});
});
},
footerCallback: function ( row, data, start, end, display ) {
var api = this.api();
var sumColumnIndexes = [7, 11, 15, 16];
// column data for "sum" columns over this page
var pageTotalColumns = api
.columns( sumColumnIndexes, { page: 'current'} )
.data();
// sum columns for "total" sum
pageTotalColumns.each(function(colData, index){
pageTotalColumns[index] = colData.reduce(function(a, b){
return a + b;
}, 0);
});
$(sumColumnIndexes).each(function(index, value){
$( api.column( value ).footer() ).text( renderNumericColumn(pageTotalColumns[index]) );
});
},
data: [] // will be added dynamic
});
statsTable.on('order.dt search.dt', function(){
statsTable.column(0, {search:'applied', order:'applied'}).nodes().each(function(cell, i){
$(cell).html( (i + 1) + '.&nbsp;&nbsp;');
});
}).draw();
var tooltipElements = dialogElement.find('[data-toggle="tooltip"]');
tooltipElements.tooltip();
};
/**
* request raw statistics data and execute callback
* @param requestData
* @param context
*/
var getStatsData = function(requestData, context){
context.dynamicArea = $('#' + config.statsContainerId + ' .pf-dynamic-area');
context.dynamicArea.showLoadingAnimation();
$.ajax({
type: 'POST',
url: Init.path.getStatisticsData,
data: requestData,
dataType: 'json',
context: context
}).done(function(data){
this.dynamicArea.hideLoadingAnimation();
this.callback(data);
}).fail(function( jqXHR, status, error) {
var reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': loadStatistics', text: reason, type: 'warning'});
});
};
/**
* update dataTable with response data
* update "header"/"filter" elements in dialog
* @param responseData
*/
var drawStatsTable = function(responseData){
var dialogElement = $('#' + config.statsDialogId);
// update filter/header -----------------------------------------------------------------------------
var navigationListElements = $('.' + config.dialogNavigationClass);
navigationListElements.find('a[data-type="typeId"][data-value="' + responseData.typeId + '"]').tab('show');
navigationListElements.find('a[data-type="period"][data-value="' + responseData.period + '"]').tab('show');
// update period pagination -------------------------------------------------------------------------
var prevButton = dialogElement.find('.' + config.dialogNavigationPrevClass);
prevButton.data('newOffset', responseData.prev);
prevButton.find('span').text('Week ' + responseData.prev.week + ', ' + responseData.prev.year);
prevButton.css('visibility', 'visible');
var nextButton = dialogElement.find('.' + config.dialogNavigationNextClass);
if(responseData.next){
nextButton.data('newOffset', responseData.next);
nextButton.find('span').text('Week ' + responseData.next.week + ', ' + responseData.next.year);
nextButton.css('visibility', 'visible');
}else{
nextButton.css('visibility', 'hidden');
}
// update current period information label ----------------------------------------------------------
// if period == "weekly" there is no "offset" -> just a single week
var offsetText = 'Week ' + responseData.start.week + ', ' + responseData.start.year;
if(responseData.period !== 'weekly'){
offsetText += ' <small><i class="fa fa-fw fa-minus"></i></small> ' +
'Week ' + responseData.offset.week + ', ' + responseData.offset.year;
}
dialogElement.find('.' + config.dialogNavigationOffsetClass)
.data('start', responseData.start)
.data('period', responseData.period)
.html(offsetText);
// clear and (re)-fill table ------------------------------------------------------------------------
var formattedData = formatStatisticsData(responseData);
this.tableApi.clear();
this.tableApi.rows.add(formattedData).draw();
};
/**
* format statistics data for dataTable
* -> e.g. format inline-chart data
* @param statsData
* @returns {Array}
*/
var formatStatisticsData = function(statsData){
var formattedData = [];
var yearStart = statsData.start.year;
var weekStart = statsData.start.week;
var weekCount = statsData.weekCount;
var yearWeeks = statsData.yearWeeks;
var tempRand = function(min, max){
return Math.random() * (max - min) + min;
};
// format/sum week statistics data for inline charts
var formatWeekData = function(weeksData){
var currentYear = yearStart;
var currentWeek = weekStart;
var formattedWeeksData = {
systemCreate: [],
systemUpdate: [],
systemDelete: [],
connectionCreate: [],
connectionUpdate: [],
connectionDelete: [],
signatureCreate: [],
signatureUpdate: [],
signatureDelete: [],
systemSum: 0,
connectionSum: 0,
signatureSum: 0
};
for(let i = 0; i < weekCount; i++){
let yearWeekProp = currentYear + '' + currentWeek;
if(weeksData.hasOwnProperty( yearWeekProp )){
let weekData = weeksData[ yearWeekProp ];
// system -------------------------------------------------------------------------------
formattedWeeksData.systemCreate.push( weekData.systemCreate );
formattedWeeksData.systemSum += parseInt( weekData.systemCreate );
formattedWeeksData.systemUpdate.push( weekData.systemUpdate );
formattedWeeksData.systemSum += parseInt( weekData.systemUpdate );
formattedWeeksData.systemDelete.push( weekData.systemDelete );
formattedWeeksData.systemSum += parseInt( weekData.systemDelete );
// connection ---------------------------------------------------------------------------
formattedWeeksData.connectionCreate.push( weekData.connectionCreate );
formattedWeeksData.connectionSum += parseInt( weekData.connectionCreate );
formattedWeeksData.connectionUpdate.push( weekData.connectionUpdate );
formattedWeeksData.connectionSum += parseInt( weekData.connectionUpdate );
formattedWeeksData.connectionDelete.push( weekData.connectionDelete );
formattedWeeksData.connectionSum += parseInt( weekData.connectionDelete );
// signature ----------------------------------------------------------------------------
formattedWeeksData.signatureCreate.push( weekData.signatureCreate );
formattedWeeksData.signatureSum += parseInt( weekData.signatureCreate );
formattedWeeksData.signatureUpdate.push( weekData.signatureUpdate );
formattedWeeksData.signatureSum += parseInt( weekData.signatureUpdate );
formattedWeeksData.signatureDelete.push( weekData.signatureDelete );
formattedWeeksData.signatureSum += parseInt( weekData.signatureDelete );
}else{
// system -------------------------------------------------------------------------------
formattedWeeksData.systemCreate.push(0);
formattedWeeksData.systemUpdate.push(0);
formattedWeeksData.systemDelete.push(0);
// connection ---------------------------------------------------------------------------
formattedWeeksData.connectionCreate.push(0);
formattedWeeksData.connectionUpdate.push(0);
formattedWeeksData.connectionDelete.push(0);
// signature ----------------------------------------------------------------------------
formattedWeeksData.signatureCreate.push(0);
formattedWeeksData.signatureUpdate.push(0);
formattedWeeksData.signatureDelete.push(0);
}
currentWeek++;
if( currentWeek > yearWeeks[currentYear] ){
currentWeek = 1;
currentYear++;
}
}
// system ---------------------------------------------------------------------------------------
formattedWeeksData.systemCreate = formattedWeeksData.systemCreate.join(',');
formattedWeeksData.systemUpdate = formattedWeeksData.systemUpdate.join(',');
formattedWeeksData.systemDelete = formattedWeeksData.systemDelete.join(',');
// connection -----------------------------------------------------------------------------------
formattedWeeksData.connectionCreate = formattedWeeksData.connectionCreate.join(',');
formattedWeeksData.connectionUpdate = formattedWeeksData.connectionUpdate.join(',');
formattedWeeksData.connectionDelete = formattedWeeksData.connectionDelete.join(',');
// signature ------------------------------------------------------------------------------------
formattedWeeksData.signatureCreate = formattedWeeksData.signatureCreate.join(',');
formattedWeeksData.signatureUpdate = formattedWeeksData.signatureUpdate.join(',');
formattedWeeksData.signatureDelete = formattedWeeksData.signatureDelete.join(',');
return formattedWeeksData;
};
$.each(statsData.statistics, function(characterId, data){
var formattedWeeksData = formatWeekData(data.weeks);
var rowData = {
character: {
id: characterId,
name: data.name,
lastLogin: data.lastLogin
},
systemCreate: {
type: 'C',
data: formattedWeeksData.systemCreate
},
systemUpdate: {
type: 'U',
data: formattedWeeksData.systemUpdate
},
systemDelete: {
type: 'D',
data: formattedWeeksData.systemDelete
},
systemSum: formattedWeeksData.systemSum,
connectionCreate: {
type: 'C',
data: formattedWeeksData.connectionCreate
},
connectionUpdate: {
type: 'U',
data: formattedWeeksData.connectionUpdate
},
connectionDelete: {
type: 'D',
data: formattedWeeksData.connectionDelete
},
connectionSum: formattedWeeksData.connectionSum,
signatureCreate: {
type: 'C',
data: formattedWeeksData.signatureCreate
},
signatureUpdate: {
type: 'U',
data: formattedWeeksData.signatureUpdate
},
signatureDelete: {
type: 'D',
data: formattedWeeksData.signatureDelete
},
signatureSum: formattedWeeksData.signatureSum,
totalSum: formattedWeeksData.systemSum + formattedWeeksData.connectionSum + formattedWeeksData.signatureSum
};
formattedData.push(rowData);
});
return formattedData;
};
/**
*
* @param dialogElement
* @returns {{}}
*/
var getRequestDataFromTabPanels = function(dialogElement){
var requestData = {};
// get data from "tab" panel links ------------------------------------------------------------------
var navigationListElements = dialogElement.find('.' + config.dialogNavigationClass);
navigationListElements.find('.' + config.dialogNavigationListItemClass + '.active a').each(function(){
var linkElement = $(this);
requestData[linkElement.data('type')]= linkElement.data('value');
});
// get current period (no offset) data (if available) -----------------------------------------------
var navigationOffsetElement = dialogElement.find('.' + config.dialogNavigationOffsetClass);
var startData = navigationOffsetElement.data('start');
var periodOld = navigationOffsetElement.data('period');
// if period switch was detected
// -> "year" and "week" should not be send
// -> start from "now"
if(
requestData.period === periodOld &&
startData
){
requestData.year = startData.year;
requestData.week = startData.week;
}
return requestData;
};
/**
* check if "activity log" type is enabled for a group
* @param type
* @returns {boolean}
*/
var isTabTypeEnabled = function(type){
var enabled = false;
switch(type){
case 'private':
if(Init.activityLogging.character){
enabled = true;
}
break;
case 'corporation':
if(
Init.activityLogging.corporation &&
Util.getCurrentUserInfo('corporationId')
){
enabled = true;
}
break;
case 'alliance':
if(
Init.activityLogging.alliance &&
Util.getCurrentUserInfo('allianceId')
){
enabled = true;
}
break;
}
return enabled;
};
/**
* show activity stats dialog
*/
$.fn.showStatsDialog = function(){
requirejs(['text!templates/dialog/stats.html', 'mustache', 'peityInlineChart'], function(template, Mustache) {
var data = {
id: config.statsDialogId,
dialogNavigationClass: config.dialogNavigationClass,
dialogNavLiClass: config.dialogNavigationListItemClass,
enablePrivateTab: isTabTypeEnabled('private'),
enableCorporationTab: isTabTypeEnabled('corporation'),
enableAllianceTab: isTabTypeEnabled('alliance'),
statsContainerId: config.statsContainerId,
statsTableId: config.statsTableId,
dialogNavigationOffsetClass: config.dialogNavigationOffsetClass,
dialogNavigationPrevClass: config.dialogNavigationPrevClass,
dialogNavigationNextClass: config.dialogNavigationNextClass
};
var content = Mustache.render(template, data);
var statsDialog = bootbox.dialog({
title: 'Statistics',
message: content,
size: 'large',
show: false,
buttons: {
close: {
label: 'close',
className: 'btn-default'
}
}
});
// model events
statsDialog.on('show.bs.modal', function(e) {
var dialogElement = $(e.target);
initStatsTable(dialogElement);
});
// Tab module events
statsDialog.find('a[data-toggle="tab"]').on('show.bs.tab', function (e, b, c) {
if( $(e.target).parent().hasClass('disabled') ){
// no action on "disabled" tabs
return false;
}
});
statsDialog.find('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
var requestData = getRequestDataFromTabPanels(statsDialog);
var tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
});
// offset change links
statsDialog.find('.' + config.dialogNavigationPrevClass + ', .' + config.dialogNavigationNextClass).on('click', function(){
var offsetData = $(this).data('newOffset');
if(offsetData){
// this should NEVER fail!
// get "base" request data (e.g. typeId, period)
// --> overwrite period data with new period data
var tmpRequestData = getRequestDataFromTabPanels(statsDialog);
var requestData = $.extend({}, tmpRequestData, offsetData);
var tableApi = statsDialog.find('#' + config.statsTableId).DataTable();
getStatsData(requestData, {tableApi: tableApi, callback: drawStatsTable});
}
});
// show dialog
statsDialog.modal('show');
});
};
});

View File

@@ -316,7 +316,7 @@ define([
var settingsDialog = bootbox.dialog({
title: 'Route settings',
message: content,
show: false,
show: false,
buttons: {
close: {
label: 'cancel',
@@ -444,7 +444,6 @@ define([
data: requestData,
context: context
}).done(function(routesData){
this.moduleElement.hideLoadingAnimation();
// execute callback
@@ -886,7 +885,14 @@ define([
};
/**
* draw route table
* @param mapId
* @param moduleElement
* @param systemFromData
* @param routesTable
* @param systemsTo
*/
var drawRouteTable = function(mapId, moduleElement, systemFromData, routesTable, systemsTo){
var requestRouteData = [];
var currentTimestamp = Util.getServerTime().getTime();

View File

@@ -0,0 +1,13 @@
// Peity jQuery plugin version 3.2.0
// (c) 2015 Ben Pickles
//
// http://benpickles.github.io/peity
//
// Released under MIT license.
(function(k,w,h,v){var d=k.fn.peity=function(a,b){y&&this.each(function(){var e=k(this),c=e.data("_peity");c?(a&&(c.type=a),k.extend(c.opts,b)):(c=new x(e,a,k.extend({},d.defaults[a],e.data("peity"),b)),e.change(function(){c.draw()}).data("_peity",c));c.draw()});return this},x=function(a,b,e){this.$el=a;this.type=b;this.opts=e},o=x.prototype,q=o.svgElement=function(a,b){return k(w.createElementNS("http://www.w3.org/2000/svg",a)).attr(b)},y="createElementNS"in w&&q("svg",{})[0].createSVGRect;o.draw=
function(){var a=this.opts;d.graphers[this.type].call(this,a);a.after&&a.after.call(this,a)};o.fill=function(){var a=this.opts.fill;return k.isFunction(a)?a:function(b,e){return a[e%a.length]}};o.prepare=function(a,b){this.$svg||this.$el.hide().after(this.$svg=q("svg",{"class":"peity"}));return this.$svg.empty().data("peity",this).attr({height:b,width:a})};o.values=function(){return k.map(this.$el.text().split(this.opts.delimiter),function(a){return parseFloat(a)})};d.defaults={};d.graphers={};d.register=
function(a,b,e){this.defaults[a]=b;this.graphers[a]=e};d.register("pie",{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(a){if(!a.delimiter){var b=this.$el.text().match(/[^0-9\.]/);a.delimiter=b?b[0]:","}b=k.map(this.values(),function(a){return 0<a?a:0});if("/"==a.delimiter)var e=b[0],b=[e,h.max(0,b[1]-e)];for(var c=0,e=b.length,t=0;c<e;c++)t+=b[c];t||(e=2,t=1,b=[0,1]);var l=2*a.radius,l=this.prepare(a.width||l,a.height||l),c=l.width(),f=l.height(),j=c/2,d=f/2,f=h.min(j,d),a=a.innerRadius;
"donut"==this.type&&!a&&(a=0.5*f);for(var r=h.PI,s=this.fill(),g=this.scale=function(a,b){var c=a/t*r*2-r/2;return[b*h.cos(c)+j,b*h.sin(c)+d]},m=0,c=0;c<e;c++){var u=b[c],i=u/t;if(0!=i){if(1==i)if(a)var i=j-0.01,p=d-f,n=d-a,i=q("path",{d:["M",j,p,"A",f,f,0,1,1,i,p,"L",i,n,"A",a,a,0,1,0,j,n].join(" ")});else i=q("circle",{cx:j,cy:d,r:f});else p=m+u,n=["M"].concat(g(m,f),"A",f,f,0,0.5<i?1:0,1,g(p,f),"L"),a?n=n.concat(g(p,a),"A",a,a,0,0.5<i?1:0,0,g(m,a)):n.push(j,d),m+=u,i=q("path",{d:n.join(" ")});
i.attr("fill",s.call(this,u,c,b));l.append(i)}}});d.register("donut",k.extend(!0,{},d.defaults.pie),function(a){d.graphers.pie.call(this,a)});d.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(a){var b=this.values();1==b.length&&b.push(b[0]);for(var e=h.max.apply(h,a.max==v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=a.strokeWidth,f=d.width(),j=d.height()-l,k=e-c,e=this.x=function(a){return a*
(f/(b.length-1))},r=this.y=function(a){var b=j;k&&(b-=(a-c)/k*j);return b+l/2},s=r(h.max(c,0)),g=[0,s],m=0;m<b.length;m++)g.push(e(m),r(b[m]));g.push(f,s);a.fill&&d.append(q("polygon",{fill:a.fill,points:g.join(" ")}));l&&d.append(q("polyline",{fill:"none",points:g.slice(2,g.length-2).join(" "),stroke:a.stroke,"stroke-width":l,"stroke-linecap":"square"}))});d.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:0.1,width:32},function(a){for(var b=this.values(),e=h.max.apply(h,a.max==
v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=d.width(),f=d.height(),j=e-c,a=a.padding,k=this.fill(),r=this.x=function(a){return a*l/b.length},s=this.y=function(a){return f-(j?(a-c)/j*f:1)},g=0;g<b.length;g++){var m=r(g+a),u=r(g+1-a)-m,i=b[g],p=s(i),n=p,o;j?0>i?n=s(h.min(e,0)):p=s(h.max(c,0)):o=1;o=p-n;0==o&&(o=1,0<e&&j&&n--);d.append(q("rect",{fill:k.call(this,i,g,b),x:m,y:n,width:u,height:o}))}})})(jQuery,document,Math);

View File

@@ -58,7 +58,7 @@
<li><i class="fa fa-plus fa-fw"></i> Add a new system at the position, you clicked at</li>
<li><i class="fa fa-object-ungroup fa-fw"></i> Select all (unlocked) systems on the map</li>
<li><i class="fa fa-filter fa-fw"></i> Filter map connections by a scope <small><a href="#" data-target="#pf-manual-scrollspy-anchor-connection-scope">more</a></small></li>
<li><i class="fa fa-eraser fa-fw"></i> Delete selected systems <small><a href="#" data-target="#pf-manual-scrollspy-anchor-system-delete">more</a></small></li>
<li><i class="fa fa-trash fa-fw"></i> Delete selected systems <small><a href="#" data-target="#pf-manual-scrollspy-anchor-system-delete">more</a></small></li>
</ul>
<h4 id="pf-manual-scrollspy-anchor-map-select"><i class="fa fa-object-ungroup fa-fw"></i> Select methods</h4>
<ul class="list-unstyled" style=" margin-left: 10px;">
@@ -98,7 +98,7 @@
<p>
Systems are represented by rectangle boxes on a map <small>(<i class="fa fa-code-fork fa-fw"></i><a href="#" data-target="#pf-manual-map">more</a>)</small>.
Pilots can interact with systems like "delete systems" <small>(<i class="fa fa-eraser fa-fw"></i><a href="#" data-target="#pf-manual-scrollspy-anchor-system-delete">more</a>)</small> or
Pilots can interact with systems like "delete systems" <small>(<i class="fa fa-trash fa-fw"></i><a href="#" data-target="#pf-manual-scrollspy-anchor-system-delete">more</a>)</small> or
"move systems" <small>(<i class="fa fa-arrows-alt fa-fw"></i><a href="#" data-target="#pf-manual-scrollspy-anchor-system-move">more</a>)</small>.
</p>
<h4 id="pf-manual-scrollspy-anchor-system-active">Active system</h4>
@@ -176,13 +176,13 @@
<li><i class="fa fa-users fa-fw"></i> Set "Rally Point" for this system <small><a href="#" data-target="#pf-manual-scrollspy-anchor-system-rally">more</a></small></li>
<li><i class="fa fa-tags fa-fw"></i> Changes the status of this system <small><a href="#" data-target="#pf-manual-scrollspy-anchor-system-status">more</a></small></li>
<li><i class="fa fa-reply fa-rotate-180 fa-fw"></i> Waypoint options for this system <small><a href="#" data-target="#pf-manual-scrollspy-anchor-system-waypoint">more</a></small></li>
<li><i class="fa fa-eraser fa-fw"></i> Delete this system and all connections <small><a href="#" data-target="#pf-manual-scrollspy-anchor-system-delete">more</a></small></li>
<li><i class="fa fa-trash fa-fw"></i> Delete this system and all connections <small><a href="#" data-target="#pf-manual-scrollspy-anchor-system-delete">more</a></small></li>
</ul>
<h4 id="pf-manual-scrollspy-anchor-system-locked"><i class="fa fa-lock fa-fw"></i> Locked system</h4>
<p>
Locked systems can´t be selected <small>(<a href="#" data-target="#pf-manual-scrollspy-anchor-map-select">more</a>)</small>,
moved <small>(<i class="fa fa-arrows-alt fa-fw"></i><a href="#" data-target="#pf-manual-scrollspy-anchor-system-move">more</a>)</small>
or deleted <small>(<i class="fa fa-eraser fa-fw"></i><a href="#" data-target="#pf-manual-scrollspy-anchor-system-delete">more</a>)</small>.
or deleted <small>(<i class="fa fa-trash fa-fw"></i><a href="#" data-target="#pf-manual-scrollspy-anchor-system-delete">more</a>)</small>.
</p>
<h4 id="pf-manual-scrollspy-anchor-system-rally"><i class="fa fa-users fa-fw"></i> Rally point</h4>
<p>
@@ -199,7 +199,7 @@
<li>add new [start] -<small> (add new waypoint in front of your waypoint queue)</small></li>
<li>add new [end] -<small> (add new waypoint to the end of your waypoint queue)</small></li>
</ul>
<h4 id="pf-manual-scrollspy-anchor-system-delete"><i class="fa fa-eraser fa-fw"></i> Delete system</h4>
<h4 id="pf-manual-scrollspy-anchor-system-delete"><i class="fa fa-trash fa-fw"></i> Delete system</h4>
<p>
Any system that is not "Locked" <small>(<i class="fa fa-lock fa-fw"></i><a href="#" data-target="#pf-manual-scrollspy-anchor-system-locked">more</a>)</small> can be deleted from a map.
</p>
@@ -263,7 +263,7 @@
<li><i class="fa fa-warning fa-fw"></i> Toggles this connection as <em>"Preserve Mass"</em> <small><a href="#" data-target="#pf-manual-scrollspy-anchor-connection-mass">more</a></small></li>
<li><i class="fa fa-crosshairs fa-fw"></i> Changes the scope of this connection <small><a href="#" data-target="#pf-manual-scrollspy-anchor-connection-scope">more</a></small></li>
<li><i class="fa fa-reply fa-rotate-180 fa-fw"></i> Changes the status of this connection <small><a href="#" data-target="#pf-manual-scrollspy-anchor-connection-status">more</a></small></li>
<li><i class="fa fa-eraser fa-fw"></i> Delete this connection <small><a href="#" data-target="#pf-manual-scrollspy-anchor-connection-delete">more</a></small></li>
<li><i class="fa fa-trash fa-fw"></i> Delete this connection <small><a href="#" data-target="#pf-manual-scrollspy-anchor-connection-delete">more</a></small></li>
</ul>
<h4 id="pf-manual-scrollspy-anchor-connection-frig"><i class="fa fa-plane fa-fw"></i> Frigate hole</h4>
<p>
@@ -276,7 +276,7 @@
Let your mates know about critical connections that should be mass-saved
(e.g. <span class="pf-system-sec-highSec">H</span> security exits) <small>(<a href="#" data-target="#pf-manual-scrollspy-anchor-system-security">more</a>)</small>.
</p>
<h4 id="pf-manual-scrollspy-anchor-connection-delete"><i class="fa fa-eraser fa-fw"></i> Delete connection</h4>
<h4 id="pf-manual-scrollspy-anchor-connection-delete"><i class="fa fa-trash fa-fw"></i> Delete connection</h4>
<p>
Connections can be detached by several ways.
</p>
@@ -322,7 +322,7 @@
<kbd>click</kbd> the browser tab were <em class="pf-brand">pathfinder</em> is open. Then press <kbd>ctrl</kbd> + <kbd>v</kbd>.<br>
<em class="pf-brand">pathfinder</em> automatically detect the data format and updates the <em>"Signature table"</em>.
</p>
<h4><i class="fa fa-eraser fa-fw"></i> Delete signatures</h4>
<h4><i class="fa fa-trash fa-fw"></i> Delete signatures</h4>
<p>
Signatures can be detached by several ways.
</p>

View File

@@ -0,0 +1,117 @@
<div id="{{id}}">
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header pull-left">
<ul class="nav navbar-nav {{dialogNavigationClass}}">
<li class="{{dialogNavLiClass}} {{^enablePrivateTab}} disabled {{/enablePrivateTab}}">
<a role="tab" data-toggle="tab" data-type="typeId" data-value="2" href="#{{statsContainerId}}">Private</a>
</li>
<li class="{{dialogNavLiClass}} active {{^enableCorporationTab}} disabled {{/enableCorporationTab}}">
<a role="tab" data-toggle="tab" data-type="typeId" data-value="3" href="#{{statsContainerId}}">Corporation</a>
</li>
<li class="{{dialogNavLiClass}} {{^enableAllianceTab}} disabled {{/enableAllianceTab}}">
<a role="tab" data-toggle="tab" data-type="typeId" data-value="4" href="#{{statsContainerId}}">Alliance</a>
</li>
</ul>
</div>
<div class="navbar-header pull-right">
<p class="navbar-text">
<i class="fa fa-filter fa-fw fa-lg"></i>
</p>
<ul class="nav navbar-nav {{dialogNavigationClass}}">
<li class="{{dialogNavLiClass}}">
<a role="tab" data-toggle="tab" data-type="period" data-value="weekly" href="#{{statsContainerId}}">Weekly</a>
</li>
<li class="{{dialogNavLiClass}} active">
<a role="tab" data-toggle="tab" data-type="period" data-value="monthly" href="#{{statsContainerId}}">Monthly</a>
</li>
<li class="{{dialogNavLiClass}}">
<a role="tab" data-toggle="tab" data-type="period" data-value="yearly" href="#{{statsContainerId}}">Yearly</a>
</li>
</ul>
</div>
</nav>
<div class="alert alert-info fade in hidden-md hidden-lg">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><i class="fa fa-close"></i></button>
<span class="txt-color txt-color-information">Info</span>
<small> Your browser window is to small. Resize it to obtain more columns.</small>
</div>
<div class="tab-content">
<div role="tabpanel" class="tab-pane fade in active" id="{{statsContainerId}}">
<h5 class="text-center">
{{! previous button }}
<span class="pull-left pf-dialog-icon-button collapsed {{dialogNavigationPrevClass}}" style="visibility: hidden; min-width: 120px">
<i class="fa fa-fw fa-arrow-left"></i>
<span>&nbsp;</span>
</span>
{{! offset label }}
<span class="{{dialogNavigationOffsetClass}}">&nbsp;</span>
{{! next button }}
<span class="pull-right pf-dialog-icon-button collapsed {{dialogNavigationNextClass}}" style="visibility: hidden; min-width: 120px">
<span>&nbsp;</span>
<i class="fa fa-fw fa-arrow-right"></i>
</span>
</h5>
<div class="pf-dynamic-area">
<table class="compact stripe order-column row-border nowrap" id="{{statsTableId}}">
<thead>
<tr>
<th colspan="4" class="separator-right">character</th>
<th colspan="4" class="separator-right hidden-xs hidden-sm">systems</th>
<th colspan="4" class="separator-right hidden-xs hidden-sm">connection</th>
<th colspan="4" class="separator-right hidden-xs hidden-sm">signatures</th>
<th class="text-right">sum</th>
</tr>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
</tr>
</thead>
<tfoot>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th class="bg-color bg-color-grayDarker"></th>
<th></th>
<th></th>
<th></th>
<th class="bg-color bg-color-grayDarker"></th>
<th></th>
<th></th>
<th></th>
<th class="bg-color bg-color-grayDarker"></th>
<th class="bg-color bg-color-tealDarker txt-color txt-color-orange"></th>
</tr>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>

View File

@@ -1,16 +1,24 @@
<div class="row text-center">
<div class="col-xs-12 col-md-8 col-md-offset-2 pf-landing-pricing-panel">
<div class="panel panel-info pricing-big">
<div class="panel panel-primary pricing-big">
<div class="panel-heading" >
<h3 class="panel-title">New release v1.1.5</h3>
<h3 class="panel-title">New release v1.1.6</h3>
</div>
<div class="panel-body no-padding text-align-center">
<div class="price-features" style="min-height: inherit;">
<ul class="list-unstyled text-left">
<li><i class="fa fa-fw fa-angle-right"></i>New: Support for multiple active characters</li>
<li><i class="fa fa-fw fa-angle-right"></i>New: Customizable default routes</li>
<li><i class="fa fa-fw fa-angle-right"></i>New: Filter option for "<em>EOL</em>" connections added to route finder</li>
<li><i class="fa fa-fw fa-angle-right"></i>New: "<em><a target="_self" href="#pf-landing-feature-user-statistics">User Statistics</a></em>" module</li>
<li>
<ul class="list-unstyled text-left">
<li><i class="fa fa-fw fa-angle-right"></i>'private', 'corporation', 'alliance' - statistics</li>
</ul>
</li>
<li><i class="fa fa-fw fa-angle-right"></i>New: "<em>Activity Log</em>" history</li>
<li>
<ul class="list-unstyled text-left">
<li><i class="fa fa-fw fa-angle-right"></i>'weekly', 'monthly', 'yearly' - filters</li>
</ul>
</li>
<li><i class="fa fa-fw fa-angle-double-right"></i>Complete <a href="javascript:void(0)" class="pf-navbar-version-info">changelog</a></li>
</ul>
</div>

View File

@@ -186,7 +186,7 @@
</a>
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4><i class="fa fa-fw fa-angle-double-right"></i>Pathfinder</h4>
<h4>Pathfinder</h4>
<ul class="fa-ul pf-landing-list">
<li><i></i><span class="txt-color txt-color-greenDark"><e class="fa fa-fw fa-lock"></e>SSL</span> encrypted connection</li>
<li><i></i>User friendly <a class="pf-navbar-manual" href="#">manual</a></li>
@@ -250,7 +250,7 @@
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4><i class="fa fa-fw fa-angle-double-right"></i>Map module</h4>
<h4>Map module</h4>
<ul class="fa-ul pf-landing-list">
<li><i></i>Live synchronisation between clients</li>
<li><i></i>Multiple map support</li>
@@ -329,7 +329,7 @@
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4><i class="fa fa-fw fa-angle-double-right"></i>System module</h4>
<h4>System module</h4>
<ul class="fa-ul pf-landing-list">
<li><i></i>Quick access to all relevant information</li>
<li>
@@ -368,7 +368,7 @@
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4><i class="fa fa-fw fa-angle-double-right"></i>Signature module</h4>
<h4>Signature module</h4>
<ul class="fa-ul pf-landing-list">
<li><i></i>Share system signature information</li>
<li><i></i>Add/Update multiple signatures at once <kbd>ctrl</kbd> + <kbd>c</kbd></li>
@@ -407,7 +407,7 @@
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4><i class="fa fa-fw fa-angle-double-right"></i>Killboard module</h4>
<h4>Killboard module</h4>
<ul class="fa-ul pf-landing-list">
<li><i></i><em><a target="_blank" href="https://github.com/zKillboard/zKillboard/wiki">zkillboard.com</a></em> API integration</li>
<li><i></i>Detailed information about all recent activities in a system</li>
@@ -440,7 +440,7 @@
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4><i class="fa fa-fw fa-angle-double-right"></i>Route module</h4>
<h4>Route module</h4>
<ul class="fa-ul pf-landing-list">
<li><i></i>Search for routes between systems</li>
<li><i></i>Predefined trade hub routes</li>
@@ -477,19 +477,20 @@
</div>
<div class="row">
<div class="col-xs-12 col-sm-3 col-lg-2">
<a class="pf-animate-on-visible pf-animate" href="public/img/landing/statistics_1.jpg" data-description="Statistics" data-gallery="#pf-gallery">
<img class="pf-landing-image-preview pf-landing-image-preview-small" data-original="public/img/landing/thumbs/statistics_1.jpg" width="160" height="160" alt="Statistics">
<a class="pf-animate-on-visible pf-animate" href="public/img/landing/statistics_1.jpg" data-description="System statistics" data-gallery="#pf-gallery">
<img class="pf-landing-image-preview pf-landing-image-preview-small" data-original="public/img/landing/thumbs/statistics_1.jpg" width="160" height="160" alt="System statistics">
</a>
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4><i class="fa fa-fw fa-angle-double-right"></i>Statistics module</h4>
<h4>System statistics module</h4>
<ul class="fa-ul pf-landing-list">
<li><i></i>Integrated live data for all systems</li>
<li><i></i>Live in-game system data</li>
<li>
<ul class="fa-ul">
<li><i></i>System jumps</li>
<li><i></i>System jump data</li>
<li><i></i>Ship/POD kills</li>
<li><i></i>NPC kills</li>
</ul>
@@ -497,9 +498,36 @@
</ul>
</div>
<div class="clearfix visible-xs visible-sm visible-md"></div>
<div class="col-xs-12 col-sm-3 col-lg-2" id="pf-landing-feature-user-statistics">
<a class="pf-animate-on-visible pf-animate" href="public/img/landing/statistics_2.jpg" data-description="User statistics" data-gallery="#pf-gallery">
<img class="pf-landing-image-preview pf-landing-image-preview-small" data-original="public/img/landing/thumbs/statistics_2.jpg" width="160" height="160" alt="User statistics">
</a>
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4>User statistics <span class="label label-success">new</span> </h4>
<ul class="fa-ul pf-landing-list">
<li><i></i>User activity logging</li>
<li>
<ul class="fa-ul">
<li><i></i>Private statistics</li>
<li><i></i>Corporation statistics</li>
<li><i></i>Alliance statistics</li>
</ul>
</li>
<li><i></i>Log history</li>
<li>
<ul class="fa-ul">
<li><i></i>Weekly</li>
<li><i></i>Monthly</li>
<li><i></i>Yearly</li>
</ul>
</li>
</ul>
</div>
</div>
<div class="row text-center hidden-xs">
@@ -634,7 +662,7 @@
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4><i class="fa fa-fw fa-server"></i>Requirements check</h4>
<h4><i class="fa fa-fw fa-server"></i> Requirements check</h4>
<ul class="fa-ul pf-landing-list">
<li><i></i>Checks your server type/status</li>
<li><i></i>Checks your installed PHP version</li>
@@ -656,7 +684,7 @@
</div>
<div class="col-xs-12 col-sm-9 col-lg-4">
<h4><i class="fa fa-fw fa-database"></i>Database setup</h4>
<h4><i class="fa fa-fw fa-database"></i> Database setup</h4>
<ul class="fa-ul pf-landing-list">
<li><i></i>Check DB connections</li>
<li><i></i>Check DB version</li>
@@ -692,7 +720,7 @@
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6 col-sm-offset-1 col-md-offset-1 col-lg-offset-1">
<h4><i class="fa fa-fw fa-angle-double-right"></i>About Me</h4>
<h4>About Me</h4>
<p>
I am playing <em><a target="_blank" href="https://www.eveonline.com/">EVE Online</a></em> since almost 4 years.
The majority of time (3+ years), my characters were a part of the <em><a target="_blank" href="https://www.themittani.com/news/j150020-three-alliances-enter-none-leave">"No Holes Barred"</a></em> alliance.
@@ -728,7 +756,7 @@
</div>
<div class="col-xs-12 col-sm-6 col-md-6 col-lg-6">
<h4><i class="fa fa-fw fa-angle-double-right"></i>Technology stack</h4>
<h4>Technology stack</h4>
<p>
If you are planning to get deeper into the project or even think about hosting it on your own webserver, you should be aware of some important key points.
<em>Pathfinder</em> is not comparable with any "out of the box" web applications or common CMS systems that come along with an auto-install feature.

View File

@@ -157,7 +157,7 @@
<div class="panel-body no-padding text-align-center">
<table class="table">
<tbody>
<thead>
<repeat group="{{ @serverInformation }}" value="{{ @information }}">
<tr>
<td>{{@information.label}}</td>
@@ -165,7 +165,7 @@
</tr>
</repeat>
</tbody>
</thead>
</table>
</div>
@@ -235,7 +235,7 @@
<div class="panel-body no-padding text-align-center">
<table class="table">
<tbody>
<thead>
<repeat group="{{ @environmentInformation }}" value="{{ @environmentData }}">
<tr>
<td>{{@environmentData.label}}</td>
@@ -251,7 +251,7 @@
</td>
</tr>
</repeat>
</tbody>
</thead>
</table>
</div>
@@ -264,11 +264,36 @@
<div class="panel panel-default pricing-big">
<div class="panel-heading text-left">
<h3 class="panel-title">Map restrictions</h3>
<h3 class="panel-title">Registration</h3>
</div>
<div class="panel-body no-padding text-align-center">
<table class="table">
<thead>
<tr>
<td>Status</td>
<td class="text-right col-md-2">
<check if="{{ @PATHFINDER.REGISTRATION.STATUS }}">
<true>
<kbd class="txt-color txt-color-success">enabled</kbd>
</true>
<false>
<kbd class="txt-color txt-color-warning">disabled</kbd>
</false>
</check>
</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
<div class="panel-footer text-left">
<h3 class="panel-title">Map restrictions</h3>
</div>
<table class="table">
<thead>
<tr>
@@ -295,17 +320,17 @@
<table class="table">
<thead>
<tr>
<td>Max. shared users (private map)</td>
<td>Max. shared users (private maps)</td>
<td class="text-right col-md-2"><kbd>{{ @PATHFINDER.MAP.PRIVATE.MAX_SHARED }}</kbd></td>
</tr>
</thead>
<tbody>
<tr>
<td>Max. shared users (corporation map)</td>
<td>Max. shared users (corporation maps)</td>
<td class="text-right"><kbd>{{ @PATHFINDER.MAP.CORPORATION.MAX_SHARED }}</kbd></td>
</tr>
<tr>
<td>Max. shared users (alliance map)</td>
<td>Max. shared users (alliance maps)</td>
<td class="text-right"><kbd>{{ @PATHFINDER.MAP.ALLIANCE.MAX_SHARED }}</kbd></td>
</tr>
</tbody>
@@ -318,32 +343,60 @@
<table class="table">
<thead>
<tr>
<td>Private map</td>
<td>Private maps</td>
<td class="text-right col-md-2"><kbd>{{ @PATHFINDER.MAP.PRIVATE.LIFETIME }}</kbd></td>
</tr>
</thead>
<tbody>
<tr>
<td>Corporation map</td>
<td>Corporation maps</td>
<td class="text-right"><kbd>{{ @PATHFINDER.MAP.CORPORATION.LIFETIME }}</kbd></td>
</tr>
<tr>
<td>Alliance map</td>
<td>Alliance maps</td>
<td class="text-right"><kbd>{{ @PATHFINDER.MAP.ALLIANCE.LIFETIME }}</kbd></td>
</tr>
</tbody>
</table>
<div class="panel-footer text-left">
<h3 class="panel-title">Registration</h3>
<h3 class="panel-title">User activity logging</h3>
</div>
<table class="table">
<thead>
<tr>
<td>Status</td>
<td>Private maps</td>
<td class="text-right col-md-2">
<check if="{{ @PATHFINDER.REGISTRATION.STATUS }}">
<check if="{{ @PATHFINDER.MAP.PRIVATE.ACTIVITY_LOGGING }}">
<true>
<kbd class="txt-color txt-color-success">enabled</kbd>
</true>
<false>
<kbd class="txt-color txt-color-warning">disabled</kbd>
</false>
</check>
</td>
</tr>
</thead>
<tbody>
<tr>
<td>Corporation maps</td>
<td class="text-right">
<check if="{{ @PATHFINDER.MAP.CORPORATION.ACTIVITY_LOGGING }}">
<true>
<kbd class="txt-color txt-color-success">enabled</kbd>
</true>
<false>
<kbd class="txt-color txt-color-warning">disabled</kbd>
</false>
</check>
</td>
</tr>
<tr>
<td>Alliance maps</td>
<td class="text-right">
<check if="{{ @PATHFINDER.MAP.ALLIANCE.ACTIVITY_LOGGING }}">
<true>
<kbd class="txt-color txt-color-success">enabled</kbd>
</true>
@@ -353,13 +406,9 @@
</check>
</td>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -19,7 +19,7 @@
&.txt-color-redDarker { color: $red-darker !important; }
&.txt-color-yellow { color: $yellow !important; }
&.txt-color-orange { color: $orange !important; }
&.txt-color-orangeDark { color: $orangeDark !important; }
&.txt-color-orangeDark { color: $orange-dark !important; }
&.txt-color-pink { color: $pink !important; }
&.txt-color-pinkDark { color: $pinkDark !important; }
&.txt-color-purple { color: $purple !important; }
@@ -58,7 +58,9 @@
&.bg-color-darken { background-color: $darken !important; }
&.bg-color-lighten { background-color: $lighten !important; }
&.bg-color-white { background-color: $white !important; }
&.bg-color-gray { background-color: $gray !important; }
&.bg-color-grayDark { background-color: $greyDark !important; }
&.bg-color-grayDarker { background-color: $gray-darker !important; }
&.bg-color-magenta { background-color: $magenta !important; }
&.bg-color-tealLighter { background-color: $teal-lighter !important; }
&.bg-color-tealDarker { background-color: $teal-darker !important; }

View File

@@ -551,7 +551,7 @@ $modal-backdrop-opacity: .3;
$modal-header-border-color: #e5e5e5;
$modal-footer-border-color: $modal-header-border-color;
$modal-lg: 820px;
$modal-lg: 1100px;
$modal-md: 600px;
$modal-sm: 300px;

View File

@@ -196,6 +196,7 @@ select:active, select:hover {
img{
width: 26px; // smaller image (default 32)
box-sizing: content-box;
border-left: 1px solid $gray;
border-right: 1px solid $gray;
}
@@ -227,6 +228,18 @@ select:active, select:hover {
}
}
// column separator
td, th{
&.separator-right{
border-right: 1px solid $gray;
}
svg.peity{
// center vertical in column
display: block;
}
}
}
// table styles ===================================================================================
@@ -293,7 +306,7 @@ table{
// navbar li =====================================================================================
.navbar-nav {
li{
li:not(.disabled){
&:hover, &.active{
&:before{

View File

@@ -194,10 +194,7 @@ html.sb-static .sb-slidebar,
*/
.sb-slide, #sb-site, .sb-site-container, .sb-slidebar {
-webkit-transition: -webkit-transform 400ms ease;
-moz-transition: -moz-transform 400ms ease;
-o-transition: -o-transform 400ms ease;
transition: transform 400ms ease;
@include transition( transform 180ms ease-out );
-webkit-transition-property: -webkit-transform, left, right; /* Add left/right for Android < 4.4. */
-webkit-backface-visibility: hidden; /* Prevents flickering. This is non essential, and you may remove it if your having problems with fixed background images in Chrome. */
}