- new "ESI monitoring" UI dialog, closed #748

- new "Redis monitoring" UI on `/setup` page, closed #745
- improved request handling for 3rd party APIs (ESI, SSO, GitHub) see [exodus4d/pathfinder_esi/README.md](https://github.com/exodus4d/pathfinder_esi/blob/b5d4b19/README.md)
- improved `/setup` page, new actions for clear cache/Redis data
This commit is contained in:
Mark Friedrich
2019-02-08 15:12:53 +01:00
parent e56740e8a0
commit e28fea9081
164 changed files with 1919 additions and 1038 deletions

View File

@@ -41,16 +41,35 @@ LANGUAGE = en-US
SEED = {{ md5(@SERVER.SERVER_NAME) }}
; Cache backend
; This sets the primary cache backend for Pathfinder. Used for e.g.:
; DB query, DB schema, HTTP response, or even simple key->value caches
; Can handle Redis, Memcache module, APC, WinCache, XCache and a filesystem-based cache.
; Hint: Redis is recommended and gives the best performance.
; Syntax: folder=[DIR] | redis=[SERVER]
; Default: folder=tmp/cache/
; Value: folder=[DIR]
; Value: FALSE
; - Disables caching
; folder=[DIR]
; - Cache data is stored on disc
; redis=[SERVER]
; - Cache data is stored in Redis (e.g. redis=localhost:6379)
; - Cache data is stored in Redis. redis=[host]:[port]:[db] (e.g. redis=localhost:6379:1)
CACHE = folder=tmp/cache/
; Cache backend for API data
; This sets the cache backend for API response data and other temp data relates to API requests.
; Response data with proper 'Expire' HTTP Header will be cached here and speed up further requests.
; As default 'API_CACHE' and 'CACHE' share the same backend (cache location)
; Hint1: You can specify e.g. a dedicated Redis DB here, then 'CACHE' and 'API_CACHE' can be cleared independently
; Hint2: Redis is recommended and gives the best performance.
; Default: {{@CACHE}}
; Value: FALSE
; - Disables caching
; folder=[DIR]
; - Cache data is stored on disc
; redis=[SERVER]
; - Cache data is stored in Redis. redis=[host]:[port]:[db] (e.g. redis=localhost:6379:2)
API_CACHE = {{@CACHE}}
; Cache backend used by PHPs Session handler.
; Hint1: Best performance and recommended configuration for Pathfinder is to configured Redis as PHPs default Session handler
; in your php.ini and set 'default' value here in order to use Redis (fastest)

View File

@@ -1,4 +1,5 @@
<?php
<?php /** @noinspection PhpUndefinedMethodInspection */
/**
* Created by PhpStorm.
* User: exodus4d
@@ -7,6 +8,8 @@
*/
namespace Controller\Api;
use lib\Config;
use Controller;
@@ -18,135 +21,68 @@ use Controller;
*/
class GitHub extends Controller\Controller {
protected function getBaseRequestOptions() : array {
return [
'timeout' => 3,
'user_agent' => $this->getUserAgent(),
'follow_location' => false // otherwise CURLOPT_FOLLOWLOCATION will fail
];
}
/**
* get HTTP request options for API (curl) request
* @return array
*/
protected function getRequestReleaseOptions() : array {
$options = $this->getBaseRequestOptions();
$options['method'] = 'GET';
return $options;
}
/**
* get HTTP request options for API (curl) request
* @param string $text
* @return array
*/
protected function getRequestMarkdownOptions(string $text) : array {
$params = [
'text' => $text,
'mode' => 'gfm',
'context' => 'exodus4d/pathfinder'
];
$options = $this->getBaseRequestOptions();
$options['method'] = 'POST';
$options['content'] = json_encode($params, JSON_UNESCAPED_SLASHES);
return $options;
}
/**
* get release information from GitHub
* @param \Base $f3
*/
public function releases(\Base $f3){
$cacheKey = 'CACHE_GITHUB_RELEASES';
$ttl = 60 * 30; // 30min
$releaseCount = 4;
if( !$f3->exists($cacheKey, $return) ){
$apiReleasePath = Config::getPathfinderData('api.git_hub') . '/repos/exodus4d/pathfinder/releases';
$apiMarkdownPath = Config::getPathfinderData('api.git_hub') . '/markdown';
$return = (object) [];
$return->releasesData = [];
$return->version = (object) [];
$return->version->current = Config::getPathfinderData('version');
$return->version->last = '';
$return->version->delta = null;
$return->version->dev = false;
// build request URL
$apiResponse = \Web::instance()->request($apiReleasePath, $this->getRequestReleaseOptions() );
$md = \Markdown::instance();
if($apiResponse['body']){
$return = (object) [];
$return->releasesData = [];
$return->version = (object) [];
$return->version->current = Config::getPathfinderData('version');
$return->version->last = '';
$return->version->delta = null;
$return->version->dev = false;
$releases = $f3->gitHubClient()->getProjectReleases('exodus4d/pathfinder', $releaseCount);
// request succeeded -> format "Markdown" to "HTML"
// result is JSON formed
$releasesData = (array)json_decode($apiResponse['body']);
// check max release count
if(count($releasesData) > $releaseCount){
$releasesData = array_slice($releasesData, 0, $releaseCount);
foreach($releases as $key => &$release){
// check version ------------------------------------------------------------------------------------------
if($key === 0){
$return->version->last = $release['name'];
if(version_compare( $return->version->current, $return->version->last, '>')){
$return->version->dev = true;
}
$md = \Markdown::instance();
foreach($releasesData as $key => &$releaseData){
// check version ----------------------------------------------------------------------------------
if($key === 0){
$return->version->last = $releaseData->tag_name;
if(version_compare( $return->version->current, $return->version->last, '>')){
$return->version->dev = true;
}
}
if(
!$return->version->dev &&
version_compare( $releaseData->tag_name, $return->version->current, '>=')
){
$return->version->delta = ($key === count($releasesData) - 1) ? '>= ' . $key : $key;
}
// format body ------------------------------------------------------------------------------------
if(isset($releaseData->body)){
$body = $releaseData->body;
// remove "update information" from release text
// -> keep everything until first "***" -> horizontal line
if( ($pos = strpos($body, '***')) !== false){
$body = substr($body, 0, $pos);
}
// convert list style
$body = str_replace(' - ', '* ', $body );
// convert Markdown to HTML -> use either gitHub API (in oder to create abs, issue links)
// -> or F3´s markdown as fallback
$markdownResponse = \Web::instance()->request($apiMarkdownPath, $this->getRequestMarkdownOptions($body) );
if($markdownResponse['body']){
$body = $markdownResponse['body'];
}else{
$body = $md->convert( trim($body) );
}
$releaseData->body = $body;
}
}
$return->releasesData = $releasesData;
$f3->set($cacheKey, $return, $ttl);
}else{
// request failed -> cache failed result (respect API request limit)
$f3->set($cacheKey, false, 60 * 15);
}
if(
!$return->version->dev &&
version_compare($release['name'], $return->version->current, '>=')
){
$return->version->delta = ($key === count($releases) - 1) ? '>= ' . $key : $key;
}
// format body ------------------------------------------------------------------------------------
$body = $release['body'];
// remove "update information" from release text
// -> keep everything until first "***" -> horizontal line
if( ($pos = strpos($body, '***')) !== false){
$body = substr($body, 0, $pos);
}
// convert list style
$body = str_replace(' - ', '* ', $body);
// convert Markdown to HTML -> use either gitHub API (in oder to create abs, issue links)
// -> or F3´s markdown as fallback
$html = $f3->gitHubClient()->markdownToHtml('exodus4d/pathfinder', $body);
if(!empty($html)){
$body = $html;
}else{
$body = $md->convert(trim($body));
}
$release['body'] = $body;
}
// set 503 if service unavailable or temp cached data = false
if( !$f3->get($cacheKey) ){
$f3->status(503);
}
$return->releasesData = $releases;
echo json_encode($f3->get($cacheKey));
echo json_encode($return);
}
}

View File

@@ -879,7 +879,7 @@ class Map extends Controller\AccessController {
// update current location
// -> suppress temporary timeout errors
$activeCharacter = $activeCharacter->updateLog(['suppressHTTPErrors' => true]);
$activeCharacter = $activeCharacter->updateLog();
if( !empty($mapIds) ){
// IMPORTANT for now -> just update a single map (save performance)

View File

@@ -590,7 +590,7 @@ class Route extends Controller\AccessController {
'connections' => $connections
];
$result = $this->getF3()->ccpClient->getRouteData($systemFromId, $systemToId, $options);
$result = $this->getF3()->ccpClient()->getRouteData($systemFromId, $systemToId, $options);
// format result ------------------------------------------------------------------------------------------

View File

@@ -135,7 +135,7 @@ class System extends Controller\AccessController {
];
foreach($postData['systemData'] as $systemData){
$response = $f3->ccpClient->setWaypoint($systemData['systemId'], $accessToken, $options);
$response = $f3->ccpClient()->setWaypoint($systemData['systemId'], $accessToken, $options);
if(empty($response)){
$return->systemData[] = $systemData;

View File

@@ -221,7 +221,7 @@ class User extends Controller\Controller{
if( $targetId = (int)$data['targetId']){
$activeCharacter = $this->getCharacter();
$response = $f3->ccpClient->openWindow($targetId, $activeCharacter->getAccessToken());
$response = $f3->ccpClient()->openWindow($targetId, $activeCharacter->getAccessToken());
if(empty($response)){
$return->targetId = $targetId;

View File

@@ -1,17 +1,18 @@
<?php
<?php /** @noinspection PhpUndefinedMethodInspection */
/**
* Created by PhpStorm.
* User: Exodus
* Date: 23.01.2016
* Time: 17:18
*
* Handles access to EVE-Online "ESI API" and "SSO" auth functions
* Handles access to EVE-Online "ESI API" and "SSO" oAuth 2.0 functions
* - Add your API credentials in "environment.ini"
* - Check "PATHFINDER.API" in "pathfinder.ini" for correct API URLs
* Hint: \Web::instance()->request automatically caches responses by their response "Cache-Control" header!
*/
namespace Controller\Ccp;
use Controller;
use Controller\Api as Api;
use Model;
@@ -124,7 +125,7 @@ class Sso extends Api\User{
* @param array $scopes
* @param string $rootAlias
*/
private function rerouteAuthorization(\Base $f3, $scopes = [], $rootAlias = 'login'){
private function rerouteAuthorization(\Base $f3, array $scopes = [], string $rootAlias = 'login'){
if( !empty( Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') ) ){
// used for "state" check between request and callback
$state = bin2hex( openssl_random_pseudo_bytes(12) );
@@ -138,7 +139,9 @@ class Sso extends Api\User{
'state' => $state
];
$ssoAuthUrl = self::getAuthorizationEndpoint() . '?' . http_build_query($urlParams, '', '&', PHP_QUERY_RFC3986 );
$ssoAuthUrl = $f3->ssoClient()->getUrl();
$ssoAuthUrl .= $f3->ssoClient()->getAuthorizationEndpointURI();
$ssoAuthUrl .= '?' . http_build_query($urlParams, '', '&', PHP_QUERY_RFC3986 );
$f3->status(302);
$f3->reroute($ssoAuthUrl);
@@ -186,22 +189,22 @@ class Sso extends Api\User{
// login succeeded -> get basic character data for current login
$verificationCharacterData = $this->verifyCharacterData($accessData->accessToken);
if( !is_null($verificationCharacterData)){
if( !empty($verificationCharacterData) ){
// check if login is restricted to a characterID
// verification available data. Data is needed for "ownerHash" check
// get character data from ESI
$characterData = $this->getCharacterData((int)$verificationCharacterData->CharacterID);
$characterData = $this->getCharacterData((int)$verificationCharacterData['characterId']);
if( isset($characterData->character) ){
// add "ownerHash" and SSO tokens
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
$characterData->character['ownerHash'] = $verificationCharacterData['characterOwnerHash'];
$characterData->character['esiAccessToken'] = $accessData->accessToken;
$characterData->character['esiAccessTokenExpires'] = $accessData->esiAccessTokenExpires;
$characterData->character['esiRefreshToken'] = $accessData->refreshToken;
$characterData->character['esiScopes'] = Lib\Util::convertScopesString($verificationCharacterData->Scopes);
$characterData->character['esiScopes'] = $verificationCharacterData['scopes'];
// add/update static character data
$characterModel = $this->updateCharacter($characterData);
@@ -363,16 +366,15 @@ class Sso extends Api\User{
* get new "access_token" by an existing "refresh_token"
* -> if "access_token" is expired, this function gets a fresh one
* @param string $refreshToken
* @param array $additionalOptions
* @return \stdClass
*/
public function refreshAccessToken(string $refreshToken, array $additionalOptions = []){
public function refreshAccessToken(string $refreshToken){
$requestParams = [
'grant_type' => 'refresh_token',
'refresh_token' => $refreshToken
];
return $this->requestAccessData($requestParams, $additionalOptions);
return $this->requestAccessData($requestParams);
}
/**
@@ -380,75 +382,41 @@ class Sso extends Api\User{
* -> this can either be done by sending a valid "authorization code"
* OR by providing a valid "refresh_token"
* @param array $requestParams
* @param array $additionalOptions
* @return \stdClass
*/
protected function requestAccessData(array $requestParams, array $additionalOptions = []){
$verifyAuthCodeUrl = self::getVerifyAuthorizationCodeEndpoint();
$verifyAuthCodeUrlParts = parse_url($verifyAuthCodeUrl);
protected function requestAccessData(array $requestParams) : \stdClass {
$accessData = (object) [];
$accessData->accessToken = null;
$accessData->refreshToken = null;
$accessData->esiAccessTokenExpires = 0;
if($verifyAuthCodeUrlParts){
$contentType = 'application/x-www-form-urlencoded';
$requestOptions = [
'timeout' => self::SSO_TIMEOUT,
'method' => 'POST',
'user_agent' => $this->getUserAgent(),
'header' => [
'Authorization: Basic ' . $this->getAuthorizationHeader(),
'Content-Type: ' . $contentType,
'Host: ' . $verifyAuthCodeUrlParts['host']
]
];
$authCodeRequestData = $this->getF3()->ssoClient()->getAccessData($this->getAuthorizationData(), $requestParams);
// content (parameters to send with)
$requestOptions['content'] = http_build_query($requestParams);
if( !empty($authCodeRequestData) ){
if( !empty($authCodeRequestData['accessToken']) ){
// accessToken is required for endpoints that require Auth
$accessData->accessToken = $authCodeRequestData['accessToken'];
}
$apiResponse = Lib\Web::instance()->request($verifyAuthCodeUrl, $requestOptions, $additionalOptions);
if( !empty($authCodeRequestData['expiresIn']) ){
// expire time for accessToken
try{
$timezone = $this->getF3()->get('getTimeZone')();
$accessTokenExpires = new \DateTime('now', $timezone);
$accessTokenExpires->add(new \DateInterval('PT' . (int)$authCodeRequestData['expiresIn'] . 'S'));
if($apiResponse['body']){
$authCodeRequestData = json_decode($apiResponse['body'], true);
if( !empty($authCodeRequestData) ){
if( isset($authCodeRequestData['access_token']) ){
// accessToken is required for endpoints that require Auth
$accessData->accessToken = $authCodeRequestData['access_token'];
}
if(isset($authCodeRequestData['expires_in'])){
// expire time for accessToken
try{
$timezone = $this->getF3()->get('getTimeZone')();
$accessTokenExpires = new \DateTime('now', $timezone);
$accessTokenExpires->add(new \DateInterval('PT' . (int)$authCodeRequestData['expires_in'] . 'S'));
$accessData->esiAccessTokenExpires = $accessTokenExpires->format('Y-m-d H:i:s');
}catch(\Exception $e){
$this->getF3()->error(500, $e->getMessage(), $e->getTrace());
}
}
if(isset($authCodeRequestData['refresh_token'])){
// this token is used to refresh/get a new access_token when expires
$accessData->refreshToken = $authCodeRequestData['refresh_token'];
}
$accessData->esiAccessTokenExpires = $accessTokenExpires->format('Y-m-d H:i:s');
}catch(\Exception $e){
$this->getF3()->error(500, $e->getMessage(), $e->getTrace());
}
}else{
self::getSSOLogger()->write(
sprintf(
self::ERROR_ACCESS_TOKEN,
print_r($requestParams, true)
)
);
}
if( !empty($authCodeRequestData['refreshToken']) ){
// this token is used to refresh/get a new access_token when expires
$accessData->refreshToken = $authCodeRequestData['refreshToken'];
}
}else{
self::getSSOLogger()->write(
sprintf(self::ERROR_CCP_SSO_URL, __METHOD__)
);
self::getSSOLogger()->write(sprintf(self::ERROR_ACCESS_TOKEN, print_r($requestParams, true)));
}
return $accessData;
@@ -458,34 +426,17 @@ class Sso extends Api\User{
* verify character data by "access_token"
* -> get some basic information (like character id)
* -> if more character information is required, use ESI "characters" endpoints request instead
* @param $accessToken
* @return mixed|null
* @param string $accessToken
* @return array
*/
public function verifyCharacterData($accessToken){
$verifyUserUrl = self::getVerifyUserEndpoint();
$verifyUrlParts = parse_url($verifyUserUrl);
$characterData = null;
public function verifyCharacterData(string $accessToken) : array {
$characterData = $this->getF3()->ssoClient()->getVerifyCharacterData($accessToken);
if($verifyUrlParts){
$requestOptions = [
'timeout' => self::SSO_TIMEOUT,
'method' => 'GET',
'user_agent' => $this->getUserAgent(),
'header' => [
'Authorization: Bearer ' . $accessToken,
'Host: ' . $verifyUrlParts['host']
]
];
$apiResponse = Lib\Web::instance()->request($verifyUserUrl, $requestOptions);
if($apiResponse['body']){
$characterData = json_decode($apiResponse['body']);
}else{
self::getSSOLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
}
if( !empty($characterData) ){
// convert string with scopes to array
$characterData['scopes'] = Lib\Util::convertScopesString($characterData['scopes']);
}else{
self::getSSOLogger()->write(sprintf(self::ERROR_CCP_SSO_URL, __METHOD__));
self::getSSOLogger()->write(sprintf(self::ERROR_VERIFY_CHARACTER, __METHOD__));
}
return $characterData;
@@ -501,7 +452,7 @@ class Sso extends Api\User{
$characterData = (object) [];
if($characterId){
$characterDataBasic = $this->getF3()->ccpClient->getCharacterData($characterId);
$characterDataBasic = $this->getF3()->ccpClient()->getCharacterData($characterId);
if( !empty($characterDataBasic) ){
// remove some "unwanted" data -> not relevant for Pathfinder
@@ -567,15 +518,16 @@ class Sso extends Api\User{
}
/**
* get "Authorization:" Header data
* get data for HTTP "Authorization:" Header
* -> This header is required for any Auth-required endpoints!
* @return string
* @return array
*/
protected function getAuthorizationHeader() : string {
return base64_encode(
Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID') . ':'
. Controller\Controller::getEnvironmentData('CCP_SSO_SECRET_KEY')
);
protected function getAuthorizationData() : array {
return [
Controller\Controller::getEnvironmentData('CCP_SSO_CLIENT_ID'),
Controller\Controller::getEnvironmentData('CCP_SSO_SECRET_KEY'),
'basic'
];
}
/**
@@ -596,18 +548,6 @@ class Sso extends Api\User{
return $url;
}
static function getAuthorizationEndpoint() : string {
return self::getSsoUrlRoot() . '/oauth/authorize';
}
static function getVerifyAuthorizationCodeEndpoint() : string {
return self::getSsoUrlRoot() . '/oauth/token';
}
static function getVerifyUserEndpoint() : string {
return self::getSsoUrlRoot() . '/oauth/verify';
}
/**
* get logger for SSO logging
* @return \Log

View File

@@ -22,7 +22,7 @@ class Universe extends Controller {
$regionsWhitelist = [
10000002 // The Forge (13 constellations -> 93 systems)
];
$regionIds = $f3->ccpClient->getUniverseRegions();
$regionIds = $f3->ccpClient()->getUniverseRegions();
$regionIds = array_intersect ($regionsWhitelist, $regionIds);
$region = Model\Universe\BasicUniverseModel::getNew('RegionModel');
@@ -43,7 +43,7 @@ class Universe extends Controller {
$constellationsWhitelist = [
20000014 // Mal (11 systems)
];
$constellationIds = $f3->ccpClient->getUniverseConstellations();
$constellationIds = $f3->ccpClient()->getUniverseConstellations();
$constellationIds = array_intersect ($constellationsWhitelist, $constellationIds);
$constellation = Model\Universe\BasicUniverseModel::getNew('ConstellationModel');
foreach($constellationIds as $constellationId){
@@ -92,7 +92,7 @@ class Universe extends Controller {
*/
protected function setupCategories(array $categoriesWhitelist = []){
$return = [];
$categoryIds = $this->getF3()->ccpClient->getUniverseCategories();
$categoryIds = $this->getF3()->ccpClient()->getUniverseCategories();
$categoryIds = array_intersect ($categoriesWhitelist, $categoryIds);
foreach($categoryIds as $categoryId){
$return[$categoryId] = $this->setupCategory($categoryId);
@@ -112,7 +112,7 @@ class Universe extends Controller {
*/
protected function setupGroups(array $groupsWhitelist = []){
$return = [];
$groupIds = $this->getF3()->ccpClient->getUniverseGroups();
$groupIds = $this->getF3()->ccpClient()->getUniverseGroups();
$groupIds = array_intersect ($groupsWhitelist, $groupIds);
/**
* @var $group Model\Universe\GroupModel
@@ -288,13 +288,13 @@ class Universe extends Controller {
$f3 = \Base::instance();
$universeNameData = [];
if( !empty($categories) && !empty($search)){
$universeIds = $f3->ccpClient->search($categories, $search, $strict);
$universeIds = $f3->ccpClient()->search($categories, $search, $strict);
if(isset($universeIds['error'])){
// ESI error
$universeNameData = $universeIds;
}elseif( !empty($universeIds) ){
$universeIds = Util::arrayFlattenByValue($universeIds);
$universeNameData = $f3->ccpClient->getUniverseNamesData($universeIds);
$universeNameData = $f3->ccpClient()->getUniverseNamesData($universeIds);
}
}
return $universeNameData;

View File

@@ -541,34 +541,73 @@ class Controller {
* @throws \Exception
*/
public function getEveServerStatus(\Base $f3){
$cacheKey = 'eve_server_status';
if( !$f3->exists($cacheKey, $return) ){
$return = (object) [];
$return->error = [];
$return->status = [
'serverName' => strtoupper( self::getEnvironmentData('CCP_ESI_DATASOURCE') ),
'serviceStatus' => 'offline'
$esiStatusVersion = 'latest';
$return = (object) [];
$return->error = [];
if($client = $f3->ccpClient()){
$return->server = [
'name' => strtoupper( self::getEnvironmentData('CCP_ESI_DATASOURCE') ),
'status' => 'offline',
'statusColor' => 'red',
];
$return->api = [
'name' => 'ESI API',
'status' => 'offline',
'statusColor' => 'red',
'url' => $client->getUrl(),
'timeout' => $client->getTimeout(),
'connectTimeout' => $client->getConnectTimeout(),
'readTimeout' => $client->getReadTimeout(),
'proxy' => ($proxy = $client->getProxy()) ? : 'false',
'verify' => $client->getVerify(),
'debug' => $client->getDebugRequests(),
'dataSource' => $client->getDataSource(),
'statusVersion' => $esiStatusVersion,
'routes' => []
];
$response = $f3->ccpClient->getServerStatus();
if( !empty($response) ){
$serverStatus = $client->getServerStatus();
if( !isset($serverStatus['error']) ){
$statusData = $serverStatus['status'];
// calculate time diff since last server restart
$timezone = $f3->get('getTimeZone')();
$dateNow = new \DateTime('now', $timezone);
$dateServerStart = new \DateTime($response['startTime']);
$dateServerStart = new \DateTime($statusData['startTime']);
$interval = $dateNow->diff($dateServerStart);
$startTimestampFormat = $interval->format('%hh %im');
if($interval->days > 0){
$startTimestampFormat = $interval->days . 'd ' . $startTimestampFormat;
}
$response['serverName'] = strtoupper( self::getEnvironmentData('CCP_ESI_DATASOURCE') );
$response['serviceStatus'] = 'online';
$response['startTime'] = $startTimestampFormat;
$return->status = $response;
$statusData['name'] = $return->server['name'];
$statusData['status'] = 'online';
$statusData['statusColor'] = 'green';
$statusData['startTime'] = $startTimestampFormat;
$return->server = $statusData;
}
$f3->set($cacheKey, $return, 60);
$apiStatus = $client->getStatusForRoutes('latest');
if( !isset($apiStatus['error']) ){
// find top status
$status = 'OK';
$color = 'green';
foreach($apiStatus['status'] as $statusData){
if('red' == $statusData['status']){
$status = 'unstable';
$color = $statusData['status'];
break;
}
if('yellow' == $statusData['status']){
$status = 'degraded';
$color = $statusData['status'];
}
}
$return->api['status'] = $status;
$return->api['statusColor'] = $color;
$return->api['routes'] = $apiStatus['status'];
}
}
@@ -795,9 +834,8 @@ class Controller {
* @param string $authType
* @return array
*/
static function getScopesByAuthType($authType = ''){
static function getScopesByAuthType(string $authType = '') : array {
$scopes = array_filter((array)self::getEnvironmentData('CCP_ESI_SCOPES'));
switch($authType){
case 'admin':
$scopesAdmin = array_filter((array)self::getEnvironmentData('CCP_ESI_SCOPES_ADMIN'));

View File

@@ -167,9 +167,6 @@ class Setup extends Controller {
// js view (file)
$f3->set('tplJsView', 'setup');
// set render functions (called within template)
$f3->set('cacheType', $this->getCacheType($f3));
// simple counter (called within template)
$counter = [];
$f3->set('tplCounter', function(string $action = 'increment', string $type = 'default', $val = 0) use (&$counter){
@@ -187,19 +184,6 @@ class Setup extends Controller {
echo \Template::instance()->render( Config::getPathfinderData('view.index') );
}
/**
* get Cache backend type for F3
* @param \Base $f3
* @return string
*/
protected function getCacheType(\Base &$f3) : string {
$cacheType = $f3->get('CACHE');
if(strpos($cacheType, 'redis') !== false){
$cacheType = 'redis';
}
return $cacheType;
}
/**
* main setup route handler
* works as dispatcher for setup functions
@@ -229,47 +213,63 @@ class Setup extends Controller {
case 'exportTable':
$this->exportTable($params['model']);
break;
case 'clearCache':
$this->clearCache($f3);
case 'clearFiles':
$this->clearFiles((string)$params['path']);
break;
case 'flushRedisDb':
$this->flushRedisDb((string)$params['host'], (int)$params['port'], (int)$params['db']);
break;
case 'invalidateCookies':
$this->invalidateCookies($f3);
break;
}
// set template data ----------------------------------------------------------------
// set environment information
$f3->set('environmentInformation', $this->getEnvironmentInformation($f3));
// ============================================================================================================
// Template data
// ============================================================================================================
// set server information
// Server -----------------------------------------------------------------------------------------------------
// Server information
$f3->set('serverInformation', $this->getServerInformation($f3));
// set requirement check information
$f3->set('checkRequirements', $this->checkRequirements($f3));
// Pathfinder directory config
$f3->set('directoryConfig', $this->getDirectoryConfig($f3));
// set php config check information
$f3->set('checkPHPConfig', $this->checkPHPConfig($f3));
// set system config check information
// Server environment variables
$f3->set('checkSystemConfig', $this->checkSystemConfig($f3));
// set map default config
// Environment ------------------------------------------------------------------------------------------------
// Server requirement
$f3->set('checkRequirements', $this->checkRequirements($f3));
// PHP config
$f3->set('checkPHPConfig', $this->checkPHPConfig($f3));
// Settings ---------------------------------------------------------------------------------------------------
// Pathfinder environment config
$f3->set('environmentInformation', $this->getEnvironmentInformation($f3));
// Pathfinder map default config
$f3->set('mapsDefaultConfig', $this->getMapsDefaultConfig($f3));
// set database connection information
// Database ---------------------------------------------------------------------------------------------------
// Database config
$f3->set('checkDatabase', $this->checkDatabase($f3, $fixColumns));
// set socket information
// Redis ------------------------------------------------------------------------------------------------------
// Redis information
$f3->set('checkRedisInformation', $this->checkRedisInformation($f3));
// Socket -----------------------------------------------------------------------------------------------------
// WebSocket information
$f3->set('socketInformation', $this->getSocketInformation());
// set index information
// Administration ---------------------------------------------------------------------------------------------
// Index information
$f3->set('indexInformation', $this->getIndexData($f3));
// set cache size
$f3->set('cacheSize', $this->getCacheData($f3));
// set Redis config check information
$f3->set('checkRedisConfig', $this->checkRedisConfig($f3));
// Filesystem (cache) size
$f3->set('checkDirSize', $this->checkDirSize($f3));
}
/**
@@ -290,6 +290,9 @@ class Setup extends Controller {
'database' => [
'icon' => 'fa-database'
],
'cache' => [
'icon' => 'fa-hdd'
],
'socket' => [
'icon' => 'fa-exchange-alt'
],
@@ -306,7 +309,7 @@ class Setup extends Controller {
* @param \Base $f3
* @return array
*/
protected function getEnvironmentInformation(\Base $f3){
protected function getEnvironmentInformation(\Base $f3) : array {
$environmentData = [];
// exclude some sensitive data (e.g. database, passwords)
$excludeVars = [
@@ -346,7 +349,7 @@ class Setup extends Controller {
* @param \Base $f3
* @return array
*/
protected function getServerInformation(\Base $f3){
protected function getServerInformation(\Base $f3) : array {
$serverInfo = [
'time' => [
'label' => 'Time',
@@ -389,15 +392,90 @@ class Setup extends Controller {
return $serverInfo;
}
/**
* get information for used directories
* @param \Base $f3
* @return array
*/
protected function getDirectoryConfig(\Base $f3) : array {
$directoryData = [
'TEMP' => [
'label' => 'TEMP',
'value' => $f3->get('TEMP'),
'check' => true,
'tooltip' => 'Temporary folder for pre compiled templates.',
'chmod' => Util::filesystemInfo($f3->get('TEMP'))['chmod']
],
'CACHE' => [
'label' => 'CACHE',
'value' => $f3->get('CACHE'),
'check' => true,
'tooltip' => 'Cache backend. Support for Redis, Memcache, APC, WinCache, XCache and a filesystem-based (default) cache.',
'chmod' => ((Config::parseDSN($f3->get('CACHE'), $confCache)) && $confCache['type'] == 'folder') ?
Util::filesystemInfo((string)$confCache['folder'])['chmod'] : ''
],
'API_CACHE' => [
'label' => 'API_CACHE',
'value' => $f3->get('API_CACHE'),
'check' => true,
'tooltip' => 'Cache backend for API related cache data. Support for Redis and a filesystem-based (default) cache.',
'chmod' => ((Config::parseDSN($f3->get('API_CACHE'), $confCacheApi)) && $confCacheApi['type'] == 'folder') ?
Util::filesystemInfo((string)$confCacheApi['folder'])['chmod'] : ''
],
'LOGS' => [
'label' => 'LOGS',
'value' => $f3->get('LOGS'),
'check' => true,
'tooltip' => 'Folder for pathfinder logs (e.g. cronjob-, error-logs, ...).',
'chmod' => Util::filesystemInfo($f3->get('LOGS'))['chmod']
],
'UI' => [
'label' => 'UI',
'value' => $f3->get('UI'),
'check' => true,
'tooltip' => 'Folder for public accessible resources (templates, js, css, images,..).',
'chmod' => Util::filesystemInfo($f3->get('UI'))['chmod']
],
'AUTOLOAD' => [
'label' => 'AUTOLOAD',
'value' => $f3->get('AUTOLOAD'),
'check' => true,
'tooltip' => 'Autoload folder for PHP files.',
'chmod' => Util::filesystemInfo($f3->get('AUTOLOAD'))['chmod']
],
'FAVICON' => [
'label' => 'FAVICON',
'value' => $f3->get('FAVICON'),
'check' => true,
'tooltip' => 'Folder for Favicons.',
'chmod' => Util::filesystemInfo($f3->get('FAVICON'))['chmod']
],
'HISTORY' => [
'label' => 'HISTORY [optional]',
'value' => Config::getPathfinderData('history.log'),
'check' => true,
'tooltip' => 'Folder for log history files. (e.g. change logs for maps).',
'chmod' => Util::filesystemInfo(Config::getPathfinderData('history.log'))['chmod']
],
'CONFIG' => [
'label' => 'CONFIG PATH [optional]',
'value' => implode(' ', (array)$f3->get('CONF')),
'check' => true,
'tooltip' => 'Folder for custom *.ini files. (e.g. when overwriting of default values in app/*.ini)'
]
];
return $directoryData;
}
/**
* check all required backend requirements
* (Fat Free Framework)
* @param \Base $f3
* @return array
*/
protected function checkRequirements(\Base $f3){
protected function checkRequirements(\Base $f3) : array {
// server type ------------------------------------------------------------------
$serverData = self::getServerData(0);
$checkRequirements = [
@@ -516,7 +594,7 @@ class Setup extends Controller {
$modNotFoundMsg = 'Module status can not be identified. '
. 'This can happen if PHP runs as \'FastCGI\'. Please check manual! ';
// mod_rewrite check ------------------------------------------------------------
// mod_rewrite check --------------------------------------------------------------------------------------
$modRewriteCheck = false;
$modRewriteVersion = 'disabled';
$modRewriteTooltip = false;
@@ -539,7 +617,7 @@ class Setup extends Controller {
'tooltip' => $modRewriteTooltip
];
// mod_headers check ------------------------------------------------------------
// mod_headers check --------------------------------------------------------------------------------------
$modHeadersCheck = false;
$modHeadersVersion = 'disabled';
$modHeadersTooltip = false;
@@ -572,6 +650,11 @@ class Setup extends Controller {
* @return array
*/
protected function checkPHPConfig(\Base $f3): array {
$memoryLimit = (int)ini_get('memory_limit');
$maxInputVars = (int)ini_get('max_input_vars');
$maxExecutionTime = (int)ini_get('max_execution_time'); // 0 == infinite
$htmlErrors = (int)ini_get('html_errors');
$phpConfig = [
'exec' => [
'label' => 'exec()',
@@ -583,29 +666,29 @@ class Setup extends Controller {
'memoryLimit' => [
'label' => 'memory_limit',
'required' => $f3->get('REQUIREMENTS.PHP.MEMORY_LIMIT'),
'version' => ini_get('memory_limit'),
'check' => ini_get('memory_limit') >= $f3->get('REQUIREMENTS.PHP.MEMORY_LIMIT'),
'version' => $memoryLimit,
'check' => $memoryLimit >= $f3->get('REQUIREMENTS.PHP.MEMORY_LIMIT'),
'tooltip' => 'PHP default = 64MB.'
],
'maxInputVars' => [
'label' => 'max_input_vars',
'required' => $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
'version' => ini_get('max_input_vars'),
'check' => ini_get('max_input_vars') >= $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
'version' => $maxInputVars,
'check' => $maxInputVars >= $f3->get('REQUIREMENTS.PHP.MAX_INPUT_VARS'),
'tooltip' => 'PHP default = 1000. Increase it in order to import larger maps.'
],
'maxExecutionTime' => [
'label' => 'max_execution_time',
'required' => $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
'version' => ini_get('max_execution_time'),
'check' => ini_get('max_execution_time') >= $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
'version' => $maxExecutionTime,
'check' => !$maxExecutionTime || $maxExecutionTime >= $f3->get('REQUIREMENTS.PHP.MAX_EXECUTION_TIME'),
'tooltip' => 'PHP default = 30. Max execution time for PHP scripts.'
],
'htmlErrors' => [
'label' => 'html_errors',
'required' => $f3->get('REQUIREMENTS.PHP.HTML_ERRORS'),
'version' => (int)ini_get('html_errors'),
'check' => (bool)ini_get('html_errors') == (bool)$f3->get('REQUIREMENTS.PHP.HTML_ERRORS'),
'version' => $htmlErrors,
'check' => (bool)$htmlErrors == (bool)$f3->get('REQUIREMENTS.PHP.HTML_ERRORS'),
'tooltip' => 'Formatted HTML StackTrace on error.'
],
[
@@ -640,70 +723,209 @@ class Setup extends Controller {
* @param \Base $f3
* @return array
*/
protected function checkRedisConfig(\Base $f3): array {
protected function checkRedisInformation(\Base $f3): array {
$redisConfig = [];
if($this->getCacheType($f3) === 'redis'){
// we need to access the "protected" member $ref from F3´s Cache class
// to get access to the underlying Redis() class
$ref = new \ReflectionObject($cache = \Cache::instance());
$prop = $ref->getProperty('ref');
$prop->setAccessible(true);
if(
extension_loaded('redis') &&
class_exists('\Redis')
){
// collection of DSN specific $conf array (host, port, db,..)
$dsnData = [];
/**
* @var $redis \Redis
* get client information for a Redis client
* @param \Redis $client
* @param array $conf
* @return array
*/
$redis = $prop->getValue($cache);
$getClientInfo = function(\Redis $client, array $conf) : array {
$redisInfo = [
'dsn' => [
'label' => 'DNS',
'value' => $conf['host'] . ':' . $conf['port']
],
'connected' => [
'label' => 'status',
'value' => $client->isConnected()
]
];
$redisServerInfo = (array)$redis->info('SERVER');
$redisMemoryInfo = (array)$redis->info('MEMORY');
$redisStatsInfo = (array)$redis->info('STATS');
return $redisInfo;
};
$redisConfig = [
'redisVersion' => [
'label' => 'redis_version',
'required' => number_format((float)$f3->get('REQUIREMENTS.REDIS.VERSION'), 1, '.', ''),
'version' => $redisServerInfo['redis_version'],
'check' => version_compare( $redisServerInfo['redis_version'], $f3->get('REQUIREMENTS.REDIS.VERSION'), '>='),
'tooltip' => 'Redis server version'
],
'maxMemory' => [
'label' => 'maxmemory',
'required' => $this->convertBytes($f3->get('REQUIREMENTS.REDIS.MAX_MEMORY')),
'version' => $this->convertBytes($redisMemoryInfo['maxmemory']),
'check' => $redisMemoryInfo['maxmemory'] >= $f3->get('REQUIREMENTS.REDIS.MAX_MEMORY'),
'tooltip' => 'Max memory limit for Redis'
],
'usedMemory' => [
'label' => 'used_memory',
'version' => $this->convertBytes($redisMemoryInfo['used_memory']),
'check' => $redisMemoryInfo['used_memory'] < $redisMemoryInfo['maxmemory'],
'tooltip' => 'Current memory used by Redis'
],
'usedMemoryPeak' => [
'label' => 'used_memory_peak',
'version' => $this->convertBytes($redisMemoryInfo['used_memory_peak']),
'check' => $redisMemoryInfo['used_memory_peak'] <= $redisMemoryInfo['maxmemory'],
'tooltip' => 'Peak memory used by Redis'
],
'maxmemoryPolicy' => [
'label' => 'maxmemory_policy',
'required' => $f3->get('REQUIREMENTS.REDIS.MAXMEMORY_POLICY'),
'version' => $redisMemoryInfo['maxmemory_policy'],
'check' => $redisMemoryInfo['maxmemory_policy'] == $f3->get('REQUIREMENTS.REDIS.MAXMEMORY_POLICY'),
'tooltip' => 'How Redis behaves if \'maxmemory\' limit reached'
],
'evictedKeys' => [
'label' => 'evicted_keys',
'version' => $redisStatsInfo['evicted_keys'],
'check' => !(bool)$redisStatsInfo['evicted_keys'],
'tooltip' => 'Number of evicted keys due to maxmemory limit'
],
'dbSize' . $redis->getDbNum() => [
'label' => 'Size DB (' . $redis->getDbNum() . ')',
'version' => $redis->dbSize(),
'check' => $redis->dbSize() > 0,
'tooltip' => 'Keys found in DB (' . $redis->getDbNum() . ') [Cache DB]'
]
/**
* get status information for a Redis client
* @param \Redis $client
* @return array
*/
$getClientStats = function(\Redis $client) use ($f3) : array {
$redisStats = [];
if($client->isConnected()){
$redisServerInfo = (array)$client->info('SERVER');
$redisMemoryInfo = (array)$client->info('MEMORY');
$redisStatsInfo = (array)$client->info('STATS');
$redisStats = [
'redisVersion' => [
'label' => 'redis_version',
'required' => number_format((float)$f3->get('REQUIREMENTS.REDIS.VERSION'), 1, '.', ''),
'version' => $redisServerInfo['redis_version'],
'check' => version_compare( $redisServerInfo['redis_version'], $f3->get('REQUIREMENTS.REDIS.VERSION'), '>='),
'tooltip' => 'Redis server version'
],
'maxMemory' => [
'label' => 'maxmemory',
'required' => $this->convertBytes($f3->get('REQUIREMENTS.REDIS.MAX_MEMORY')),
'version' => $this->convertBytes($redisMemoryInfo['maxmemory']),
'check' => $redisMemoryInfo['maxmemory'] >= $f3->get('REQUIREMENTS.REDIS.MAX_MEMORY'),
'tooltip' => 'Max memory limit for Redis'
],
'usedMemory' => [
'label' => 'used_memory',
'version' => $this->convertBytes($redisMemoryInfo['used_memory']),
'check' => $redisMemoryInfo['used_memory'] < $redisMemoryInfo['maxmemory'],
'tooltip' => 'Current memory used by Redis'
],
'usedMemoryPeak' => [
'label' => 'used_memory_peak',
'version' => $this->convertBytes($redisMemoryInfo['used_memory_peak']),
'check' => $redisMemoryInfo['used_memory_peak'] <= $redisMemoryInfo['maxmemory'],
'tooltip' => 'Peak memory used by Redis'
],
'maxmemoryPolicy' => [
'label' => 'maxmemory_policy',
'required' => $f3->get('REQUIREMENTS.REDIS.MAXMEMORY_POLICY'),
'version' => $redisMemoryInfo['maxmemory_policy'],
'check' => $redisMemoryInfo['maxmemory_policy'] == $f3->get('REQUIREMENTS.REDIS.MAXMEMORY_POLICY'),
'tooltip' => 'How Redis behaves if \'maxmemory\' limit reached'
],
'evictedKeys' => [
'label' => 'evicted_keys',
'version' => $redisStatsInfo['evicted_keys'],
'check' => !(bool)$redisStatsInfo['evicted_keys'],
'tooltip' => 'Number of evicted keys due to maxmemory limit'
],
[
'label' => 'Databases'
]
];
}
return $redisStats;
};
/**
* get database status for current selected db
* @param \Redis $client
* @param string $tag
* @return array
*/
$getDatabaseStatus = function(\Redis $client, string $tag) : array {
$redisDatabases = [];
if($client->isConnected()){
$dbNum = $client->getDbNum();
$dbSize = $client->dbSize();
$redisDatabases = [
'db_' . $dbNum => [
'label' => '<i class="fas fa-fw fa-database"></i> db(' . $dbNum . ') : ' . $tag,
'version' => $dbSize . ' keys',
'check' => $dbSize > 0,
'tooltip' => 'Keys in db(' . $dbNum . ')',
'task' => [
[
'action' => http_build_query([
'action' => 'flushRedisDb',
'host' => $client->getHost(),
'port' => $client->getPort(),
'db' => $dbNum
]) . '#pf-setup-cache',
'label' => 'Flush',
'icon' => 'fa-trash',
'btn' => 'btn-danger' . (($dbSize > 0) ? '' : ' disabled')
]
]
]
];
}
return $redisDatabases;
};
/**
* build (modify) $redisConfig with DNS $conf data
* @param array $conf
*/
$buildRedisConfig = function(array $conf) use (&$redisConfig, $getClientInfo, $getClientStats, $getDatabaseStatus){
if($conf['type'] == 'redis'){
// is Redis -> group all DNS by host:port
$client = new \Redis();
try{
$client->connect($conf['host'], $conf['port'], 0.3);
if(isset($conf['db'])) {
$client->select($conf['db']);
}
$conf['db'] = $client->getDbNum();
}catch(\RedisException $e){
// connection failed
}
if(!array_key_exists($uid = $conf['host'] . ':' . $conf['port'], $redisConfig)){
$redisConfig[$uid] = $getClientInfo($client, $conf);
$redisConfig[$uid]['status'] = $getClientStats($client) + $getDatabaseStatus($client, $conf['tag']);
}elseif(!array_key_exists($uidDb = 'db_' . $conf['db'], $redisConfig[$uid]['status'])){
$redisConfig[$uid]['status'] += $getDatabaseStatus($client, $conf['tag']);
}else{
$redisConfig[$uid]['status'][$uidDb]['label'] .= '; ' . $conf['tag'];
}
$client->close();
}
};
// potential Redis caches ---------------------------------------------------------------------------------
$redisCaches = [
'CACHE' => $f3->get('CACHE'),
'API_CACHE' => $f3->get('API_CACHE')
];
foreach($redisCaches as $tag => $dsn){
if(Config::parseDSN($dsn, $conf)){
$conf['tag'] = $tag;
$dsnData[] = $conf;
}
}
// if Session handler is also Redis -> add this as well ---------------------------------------------------
// -> the DSN format is not the same, convert URL format into DSN
if(
strtolower(session_module_name()) == 'redis' &&
($parts = parse_url(strtolower(session_save_path())))
){
// parse URL parameters
parse_str((string)$parts['query'], $params);
$conf = [
'type' => 'redis',
'host' => $parts['host'],
'port' => $parts['port'],
'db' => !empty($params['database']) ? (int)$params['database'] : 0,
'tag' => 'SESSION'
];
$dsnData[] = $conf;
}
// sort all $dsnData by 'db' number -----------------------------------------------------------------------
usort($dsnData, function($a, $b){
return $a['db'] <=> $b['db'];
});
foreach($dsnData as $conf){
$buildRedisConfig($conf);
}
}
return $redisConfig;
@@ -989,14 +1211,14 @@ class Setup extends Controller {
$changedIndex = false;
$addConstraints = [];
// set (new) column information -------------------------------------------------------
// set (new) column information -----------------------------------------------------------
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['exists'] = true;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentType'] = $currentColType;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentNullable'] = $hasNullable;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentIndex'] = $hasIndex;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['currentUnique'] = $hasUnique;
// check constraint -------------------------------------------------------------------
// check constraint -----------------------------------------------------------------------
if(isset($fieldConf['constraint'])){
// add or update constraints
foreach((array)$fieldConf['constraint'] as $constraintData){
@@ -1022,7 +1244,7 @@ class Setup extends Controller {
}
}
// check type changed -----------------------------------------------------------------
// check type changed ---------------------------------------------------------------------
if(
$fieldConf['type'] !== 'JSON' &&
!$schema->isCompatible($fieldConf['type'], $currentColType)
@@ -1033,14 +1255,14 @@ class Setup extends Controller {
$tableStatusCheckCount++;
}
// check if column nullable changed ---------------------------------------------------
// check if column nullable changed -------------------------------------------------------
if( $currentNullable != $fieldConf['nullable']){
$changedNullable = true;
$columnStatusCheck = false;
$tableStatusCheckCount++;
}
// check if column index changed ------------------------------------------------------
// check if column index changed ----------------------------------------------------------
$indexUpdate = false;
$indexKey = (bool)$hasIndex;
$indexUnique = (bool)$hasUnique;
@@ -1054,7 +1276,7 @@ class Setup extends Controller {
$indexKey = (bool)$fieldConf['index'];
}
// check if column unique changed -----------------------------------------------------
// check if column unique changed ---------------------------------------------------------
if($currentColIndexData['unique'] != $fieldConf['unique']){
$changedUnique = true;
$columnStatusCheck = false;
@@ -1064,7 +1286,7 @@ class Setup extends Controller {
$indexUnique = (bool)$fieldConf['unique'];
}
// build table with changed columns ---------------------------------------------------
// build table with changed columns -------------------------------------------------------
if(!$columnStatusCheck || !$foreignKeyStatusCheck){
if(!$columnStatusCheck ){
@@ -1106,7 +1328,7 @@ class Setup extends Controller {
}
}
// set (new) column information -------------------------------------------------------
// set (new) column information -----------------------------------------------------------
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedType'] = $changedType;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedNullable'] = $changedNullable;
$requiredTables[$requiredTableName]['fieldConf'][$columnName]['changedUnique'] = $changedUnique;
@@ -1360,7 +1582,7 @@ class Setup extends Controller {
[
'action' => 'clearIndex',
'label' => 'Clear',
'icon' => 'fa-times',
'icon' => 'fa-trash',
'btn' => 'btn-danger'
],[
'action' => 'buildIndex',
@@ -1473,39 +1695,96 @@ class Setup extends Controller {
}
/**
* get cache folder size as string
* get cache folder size
* @param \Base $f3
* @return array
*/
protected function getCacheData(\Base $f3){
protected function checkDirSize(\Base $f3) : array {
// limit shown cache size. Reduce page load on big cache. In Bytes
$maxBytes = 10 * 1024 * 1024; // 10MB
$dirTemp = (string)$f3->get('TEMP');
$cacheDsn = (string)$f3->get('CACHE');
Config::parseDSN($cacheDsn, $conf);
// if 'CACHE' is e.g. redis=... -> show default dir for cache
$dirCache = $conf['type'] == 'folder' ? $conf['folder'] : $dirTemp . 'cache/';
// get all cache -----------------------------------------------------------------------------------------
$cacheFilesAll = Search::getFilesByMTime( $f3->get('TEMP') );
$dirAll = [
'TEMP' => [
'label' => 'Temp dir',
'path' => $dirTemp
],
'CACHE' => [
'label' => 'Cache dir',
'path' => $dirCache
]
];
$maxHitAll = false;
$bytesAll = 0;
foreach($cacheFilesAll as $filename => $file) {
$bytesAll += $file->getSize();
}
// get data cache -----------------------------------------------------------------------------------------
$cacheFilesData = Search::getFilesByMTime( $f3->get('TEMP') . 'cache/' );
$bytesData = 0;
foreach($cacheFilesData as $filename => $file) {
$bytesData += $file->getSize();
foreach($dirAll as $key => $dirData){
$maxHit = false;
$bytes = 0;
$files = Search::getFilesByMTime($dirData['path']);
foreach($files as $filename => $file) {
$bytes += $file->getSize();
if($bytes > $maxBytes){
$maxHit = $maxHitAll = true;
break;
}
}
$bytesAll += $bytes;
$dirAll[$key]['size'] = ($maxHit ? '>' : '') . $this->convertBytes($bytes);
$dirAll[$key]['task'] = [
[
'action' => http_build_query([
'action' => 'clearFiles',
'path' => $dirData['path']
]),
'label' => 'Delete files',
'icon' => 'fa-trash',
'btn' => 'btn-danger' . (($bytes > 0) ? '' : ' disabled')
]
];
}
return [
'all' => $this->convertBytes($bytesAll),
'data' => $this->convertBytes($bytesData),
'template' => $this->convertBytes($bytesAll - $bytesData)
'sizeAll' => ($maxHitAll ? '>' : '') . $this->convertBytes($bytesAll),
'dirAll' => $dirAll
];
}
/**
* clear all cached files
* @param \Base $f3
* clear directory
* @param string $path
*/
protected function clearCache(\Base $f3){
$f3->clear('CACHE');
protected function clearFiles(string $path){
$files = Search::getFilesByMTime($path);
foreach($files as $filename => $file){
/**
* @var $file \SplFileInfo
*/
if($file->isFile()){
if($file->isWritable()){
unlink($file->getRealPath());
}
}
}
}
/**
* clear all key in a specific Redis database
* @param string $host
* @param int $port
* @param int $db
*/
protected function flushRedisDb(string $host, int $port, int $db = 0){
$client = new \Redis();
$client->connect($host, $port, 0.3);
$client->select($db);
$client->flushDB();
$client->close();
}
/**

View File

@@ -104,13 +104,13 @@ class CcpSystemsUpdate extends AbstractCron {
// get current jump data --------------------------------------------------------------------------------------
$time_start = microtime(true);
$jumpData = $f3->ccpClient->getUniverseJumps();
$jumpData = $f3->ccpClient()->getUniverseJumps();
$time_end = microtime(true);
$execTimeGetJumpData = $time_end - $time_start;
// get current kill data --------------------------------------------------------------------------------------
$time_start = microtime(true);
$killData = $f3->ccpClient->getUniverseKills();
$killData = $f3->ccpClient()->getUniverseKills();
$time_end = microtime(true);
$execTimeGetKillData = $time_end - $time_start;

View File

@@ -67,8 +67,7 @@ class CharacterUpdate extends AbstractCron {
if(is_object($characterLog->characterId)){
// force characterLog as "updated" even if no changes were made
$characterLog->characterId->updateLog([
'markUpdated' => true,
'suppressHTTPErrors' => true
'markUpdated' => true
]);
}else{
// character_log does not have a character assigned -> delete

View File

@@ -177,7 +177,7 @@ class Universe extends AbstractCron {
switch($type){
case 'system':
// load systems + dependencies (planets, star, types,...)
$ids = $f3->ccpClient->getUniverseSystems();
$ids = $f3->ccpClient()->getUniverseSystems();
$modelClass = 'SystemModel';
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
$model->loadById($id);
@@ -186,7 +186,7 @@ class Universe extends AbstractCron {
break;
case 'stargate':
// load all stargates. Systems must be present first!
$ids = $f3->ccpClient->getUniverseSystems();
$ids = $f3->ccpClient()->getUniverseSystems();
$modelClass = 'SystemModel';
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
$model->loadById($id);
@@ -195,7 +195,7 @@ class Universe extends AbstractCron {
break;
case 'index_system':
// setup system index, Systems must be present first!
$ids = $f3->ccpClient->getUniverseSystems();
$ids = $f3->ccpClient()->getUniverseSystems();
$modelClass = 'SystemModel';
$setupModel = function(Model\Universe\SystemModel &$model, int $id){
$model->getById($id); // no loadById() here! would take "forever" when system not exists and build up first...

View File

@@ -0,0 +1,323 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 26.12.2018
* Time: 17:41
*/
namespace lib\api;
use Cache\Adapter\Filesystem\FilesystemCachePool;
use Cache\Adapter\PHPArray\ArrayCachePool;
use Cache\Adapter\Redis\RedisCachePool;
use Cache\Namespaced\NamespacedCachePool;
use League\Flysystem\Adapter\Local;
use League\Flysystem\Filesystem;
use lib\Config;
use lib\Util;
use lib\logging;
use controller\LogController;
use Exodus4D\ESI\Client\ApiInterface;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Http\Message\RequestInterface;
abstract class AbstractClient extends \Prefab {
const ERROR_CLIENT_INVALID = "HTTP API client not found → Check installed Composer packages";
/**
* @var string|null
*/
const CLIENT_NAME = null;
/**
* @var string|null
*/
protected $clientName = null;
/**
* @var ApiInterface|null
*/
protected $client = null;
/**
* PSR-6 compatible CachePool instance
* -> can be Redis, Filesystem or Array cachePool
* -> used by e.g. GuzzleCacheMiddleware
* @var CacheItemPoolInterface|null
*/
protected $cachePool = null;
/**
* @param \Base $f3
* @return ApiInterface|null
*/
abstract protected function getClient(\Base $f3) : ?ApiInterface;
/**
* get userAgent
* @return string
*/
protected function getUserAgent() : string {
$userAgent = '';
$userAgent .= Config::getPathfinderData('name');
$userAgent .= ' - ' . Config::getPathfinderData('version');
$userAgent .= ' | ' . Config::getPathfinderData('contact');
$userAgent .= ' (' . $_SERVER['SERVER_NAME'] . ')';
return $userAgent;
}
/**
* returns a new Log object used within the Api for logging
* @return \Closure
*/
protected function newLog() : \Closure {
return function(string $action, string $level = 'warning') : logging\LogInterface {
$log = new logging\ApiLog($action, $level);
$log->addHandler('stream', 'json', $this->getStreamConfig($action));
return $log;
};
}
/**
* returns a new instance of PSR-6 compatible CacheItemPoolInterface
* -> this Cache backend will be used across Guzzle Middleware
* e.g. GuzzleCacheMiddleware
* @see http://www.php-cache.com
* @param \Base $f3
* @return \Closure
*/
protected function getCachePool(\Base $f3) : \Closure {
// determine cachePool options
$poolConfig = $this->getCachePoolConfig($f3);
return function() use ($poolConfig) : ?CacheItemPoolInterface {
// an active CachePool should be re-used
// -> no need for e.g. a new Redis->connect()
// and/or re-init when it is used the next time
if(!is_null($this->cachePool)){
return $this->cachePool;
}
// Redis is preferred option (best performance) -----------------------------------------------------------
if(
$poolConfig['type'] == 'redis' &&
extension_loaded('redis') &&
class_exists('\Redis') &&
class_exists(RedisCachePool::class)
){
$client = new \Redis();
if(
$client->connect(
$poolConfig['host'],
$poolConfig['port'],
Config::REDIS_OPT_TIMEOUT,
null,
Config::REDIS_OPT_RETRY_INTERVAL,
Config::REDIS_OPT_READ_TIMEOUT
)
){
if(isset($poolConfig['db'])){
$client->select($poolConfig['db']);
}
$poolRedis = new RedisCachePool($client);
// RedisCachePool supports "Hierarchy" store slots
// -> "Hierarchy" support is required to use it in a NamespacedCachePool
// This helps to separate keys by a namespace
// @see http://www.php-cache.com/en/latest/
$this->cachePool = new NamespacedCachePool($poolRedis, static::CLIENT_NAME);
}
}
// Filesystem is second option and fallback for failed Redis pool -----------------------------------------
if(
is_null($this->cachePool) &&
in_array($poolConfig['type'], ['redis', 'folder']) &&
class_exists(FilesystemCachePool::class)
){
$filesystemAdapter = new Local('./');
$filesystem = new Filesystem($filesystemAdapter);
$poolFilesystem = new FilesystemCachePool($filesystem);
$poolFilesystem->setFolder($poolConfig['folder']);
$this->cachePool = $poolFilesystem;
}
// Array cache pool fallback (not persistent) -------------------------------------------------------------
if(
is_null($this->cachePool) &&
in_array($poolConfig['type'], ['redis', 'folder', 'array']) &&
class_exists(ArrayCachePool::class)
){
$this->cachePool = new ArrayCachePool(2000);
}
return $this->cachePool;
};
}
/**
* get cachePool config from [D]ata [S]ource [N]ame string
* @param \Base $f3
* @return array
*/
protected function getCachePoolConfig(\Base $f3) : array {
$dsn = (string)$f3->get('API_CACHE');
// fallback
$conf = ['type' => 'array'];
if(!empty($folder = (string)$f3->get('TEMP'))){
// filesystem (better than 'array' cache)
$conf = [
'type' => 'folder',
'folder' => $folder . 'cache/'
];
}
// redis or filesystem -> overwrites $conf
Config::parseDSN($dsn, $conf);
return $conf;
}
/**
* return callback function that expects a $request and checks
* whether it should be logged (in case of errors)
* @param \Base $f3
* @return \Closure
*/
protected function isLoggable(\Base $f3) : \Closure {
return function(RequestInterface $request) use ($f3) : bool {
// we need the timestamp for $request that should be checked
// -> we assume $request was "recently" send. -> current server time is used for check
$requestTime = $f3->get('getDateTime')();
// ... "interpolate" time to short interval
// -> this might help to re-use sequential calls of this method
Util::roundToInterval($requestTime);
// check if request was send within ESI downTime range
// -> errors during downTime should not be logged
$inDowntimeRange = Config::inDownTimeRange($requestTime);
return !$inDowntimeRange;
};
}
/**
* get Logger
* @param string $ype
* @return \Log
*/
protected function getLogger(string $ype = 'ERROR') : \Log {
return LogController::getLogger($ype);
}
/**
* get error msg for missing $this->client class
* @param string $class
* @return string
*/
protected function getMissingClassError(string $class) : string {
return sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, $class);
}
/**
* get error msg for undefined method in $this->client class
* @param string $class
* @param string $method
* @return string
*/
protected function getMissingMethodError(string $class, string $method) : string {
return sprintf(Config::ERROR_METHOD_NOT_EXISTS_COMPOSER, $method, $class);
}
/**
* get config for stream logging
* @param string $logFileName
* @param bool $abs
* @return \stdClass
*/
protected function getStreamConfig(string $logFileName, bool $abs = false) : \stdClass {
$f3 = \Base::instance();
$config = (object) [];
$config->stream = '';
if( $f3->exists('LOGS', $dir) ){
$config->stream .= $abs ? $f3->get('ROOT') . '/' : './';
$config->stream .= $dir . $logFileName . '.log';
$config->stream = $f3->fixslashes($config->stream);
}
return $config;
}
/**
* call request API data
* @param string $name
* @param array $arguments
* @return array|mixed
*/
public function __call(string $name, array $arguments = []){
$return = [];
if(is_object($this->client)){
if( method_exists($this->client, $name) ){
$return = call_user_func_array([$this->client, $name], $arguments);
}else{
$errorMsg = $this->getMissingMethodError(get_class($this->client), $name);
$this->getLogger('ERROR')->write($errorMsg);
\Base::instance()->error(501, $errorMsg);
}
}else{
\Base::instance()->error(501, self::ERROR_CLIENT_INVALID);
}
return $return;
}
/**
* init web client on __invoke()
* -> no need to init client on __construct()
* maybe it is nerer used...
* @return AbstractClient
*/
function __invoke() : self {
$f3 = \Base::instance();
if(
!($this->client instanceof ApiInterface) &&
($this->getClient($f3) instanceof ApiInterface)
){
// web client not initialized
$client = $this->getClient($f3);
$client->setTimeout(3);
$client->setUserAgent($this->getUserAgent());
$client->setDecodeContent('gzip, deflate');
$client->setDebugLevel($f3->get('DEBUG'));
$client->setNewLog($this->newLog());
$client->setIsLoggable($this->isLoggable($f3));
$client->setLogStats(true); // add cURL stats (e.g. transferTime) to logged requests
$client->setLogCache(true); // add cache info (e.g. from cached) to logged requests
//$client->setLogAllStatus(true); // log all requests regardless of response HTTP status code
$client->setLogFile('esi_requests');//
$client->setRetryLogFile('esi_retry_requests');
$client->setCacheDebug(true);
$client->setCachePool($this->getCachePool($f3));
// use local proxy server for debugging requests
#$client->setProxy('127.0.0.1:8888');
// disable SSL certificate verification -> allow proxy to decode(view) request
#$client->setVerify(false);
//$client->setDebugRequests(true);
$this->client = $client;
}
return $this;
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 26.12.2018
* Time: 17:43
*/
namespace lib\api;
use lib\Config;
use Exodus4D\ESI\Client\ESI as Client;
use Exodus4D\ESI\Client\ApiInterface;
class CcpClient extends AbstractClient {
/**
* @var string
*/
const CLIENT_NAME = 'ccpClient';
/**
* @param \Base $f3
* @return ApiInterface|null
*/
protected function getClient(\Base $f3) : ?ApiInterface {
$client = null;
if(class_exists(Client::class)){
$client = new Client(Config::getEnvironmentData('CCP_ESI_URL'));
$client->setDataSource(Config::getEnvironmentData('CCP_ESI_DATASOURCE'));
}else{
$this->getLogger()->write($this->getMissingClassError(Client::class));
}
return $client;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 29.01.2019
* Time: 22:23
*/
namespace lib\api;
use lib\Config;
use Exodus4D\ESI\Client\Github as Client;
use Exodus4D\ESI\Client\ApiInterface;
class GitHubClient extends AbstractClient {
/**
* @var string
*/
const CLIENT_NAME = 'gitHubClient';
/**
* @param \Base $f3
* @return ApiInterface|null
*/
protected function getClient(\Base $f3) : ?ApiInterface {
$client = null;
if(class_exists(Client::class)){
$client = new Client(Config::getPathfinderData('api.git_hub'));
}else{
$this->getLogger()->write($this->getMissingClassError(Client::class));
}
return $client;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 26.12.2018
* Time: 17:39
*/
namespace lib\api;
use lib\Config;
use Exodus4D\ESI\Client\SSO as Client;
use Exodus4D\ESI\Client\ApiInterface;
class SsoClient extends AbstractClient {
/**
* @var string
*/
const CLIENT_NAME = 'ssoClient';
/**
* @param \Base $f3
* @return ApiInterface|null
*/
protected function getClient(\Base $f3) : ?ApiInterface {
$client = null;
if(class_exists(Client::class)){
$client = new Client(Config::getEnvironmentData('CCP_SSO_URL'));
}else{
$this->getLogger()->write($this->getMissingClassError(Client::class));
}
return $client;
}
}

View File

@@ -1,90 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus4D
* Date: 26.03.2017
* Time: 19:17
*/
namespace lib;
use controller\LogController;
use \Exodus4D\ESI\ESI as ApiClient;
class CcpClient extends \Prefab {
private $apiClient;
public function __construct(\Base $f3){
$this->apiClient = $this->getClient($f3);
$f3->set('ccpClient', $this);
}
/**
* get ApiClient instance
* @param \Base $f3
* @return ApiClient|null
*/
protected function getClient(\Base $f3){
$client = null;
if(class_exists(ApiClient::class)){
$client = new ApiClient();
$client->setUrl( Config::getEnvironmentData('CCP_ESI_URL') );
$client->setDatasource( Config::getEnvironmentData('CCP_ESI_DATASOURCE') );
$client->setUserAgent($this->getUserAgent());
$client->setDebugLevel($f3->get('DEBUG'));
//$client->setDebugLogRequests(true);
}else{
LogController::getLogger('ERROR')->write(sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
}
return $client;
}
/**
* @return string
*/
protected function getUserAgent(){
$userAgent = '';
$userAgent .= Config::getPathfinderData('name');
$userAgent .= ' - ' . Config::getPathfinderData('version');
$userAgent .= ' | ' . Config::getPathfinderData('contact');
$userAgent .= ' (' . $_SERVER['SERVER_NAME'] . ')';
return $userAgent;
}
/**
* get error msg for undefined method in ApiClient() class
* @param $method
* @return string
*/
protected function getMissingMethodError($method){
return "Method '" . $method . "()' not found in class '" . get_class($this->apiClient) . "'. -> Check installed Composer package version.'";
}
/**
* call request API data
* @param $name
* @param $arguments
* @return array|mixed
*/
public function __call($name, $arguments){
$return = [];
if(is_object($this->apiClient)){
if( method_exists($this->apiClient, $name) ){
$return = call_user_func_array([$this->apiClient, $name], $arguments);
}else{
LogController::getLogger('ERROR')->write($this->getMissingMethodError($name));
\Base::instance()->error(501, $this->getMissingMethodError($name));
}
}else{
LogController::getLogger('ERROR')->write(sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
\Base::instance()->error(501, sprintf(Config::ERROR_CLASS_NOT_EXISTS_COMPOSER, ApiClient::class));
}
return $return;
}
}

View File

@@ -8,8 +8,10 @@
namespace lib;
use controller\LogController;
use Exception;
use lib\api\CcpClient;
use lib\api\GitHubClient;
use lib\api\SsoClient;
class Config extends \Prefab {
@@ -20,6 +22,29 @@ class Config extends \Prefab {
const CACHE_KEY_SOCKET_VALID = 'CACHED_SOCKET_VALID';
const CACHE_TTL_SOCKET_VALID = 60;
// ================================================================================================================
// Redis
// ================================================================================================================
/**
* Redis connect timeout (seconds)
*/
const REDIS_OPT_TIMEOUT = 2;
/**
* Redis read timeout (seconds)
*/
const REDIS_OPT_READ_TIMEOUT = 10;
/**
* redis retry interval (milliseconds)
*/
const REDIS_OPT_RETRY_INTERVAL = 200;
// ================================================================================================================
// EVE downtime
// ================================================================================================================
/**
* SSO downtime length (estimation), minutes
*/
@@ -30,7 +55,8 @@ class Config extends \Prefab {
*/
const DOWNTIME_BUFFER = 1;
const ERROR_CLASS_NOT_EXISTS_COMPOSER = 'Class "%s" not found. -> Check installed Composer packages';
const ERROR_CLASS_NOT_EXISTS_COMPOSER = 'Class "%s" not found. Check installed Composer packages';
const ERROR_METHOD_NOT_EXISTS_COMPOSER = 'Method "%s()" not found in class "%s". → Check installed Composer packages';
/**
@@ -59,10 +85,21 @@ class Config extends \Prefab {
// -> overwrites default configuration
$this->setHiveVariables($f3);
// set global function for current DateTimeZone()
$f3->set('getTimeZone', function() use ($f3){
// set global getter for \DateTimeZone
$f3->set('getTimeZone', function() use ($f3) : \DateTimeZone {
return new \DateTimeZone( $f3->get('TZ') );
});
// set global getter for new \DateTime
$f3->set('getDateTime', function(string $time = 'now', ?\DateTimeZone $timeZone = null) use ($f3) : \DateTime {
$timeZone = $timeZone ? : $f3->get('getTimeZone')();
return new \DateTime($time, $timeZone);
});
// lazy init Web Api clients
$f3->set(SsoClient::CLIENT_NAME, SsoClient::instance());
$f3->set(CcpClient::CLIENT_NAME, CcpClient::instance());
$f3->set(GitHubClient::CLIENT_NAME, GitHubClient::instance());
}
/**
@@ -410,6 +447,33 @@ class Config extends \Prefab {
return $status;
}
/**
* parse [D]ata [S]ource [N]ame string from *.ini into $conf parts
* -> $dsn = redis=localhost:6379:2
* $conf = ['type' => 'redis', 'host' => 'localhost', 'port' => 6379, 'db' => 2]
* -> some $conf values might be NULL if not found in $dsn!
* -> some missing values become defaults
* @param string $dsn
* @param array|null $conf
* @return bool
*/
static function parseDSN(string $dsn, ?array &$conf = []) : bool {
// reset reference
if($matches = (bool)preg_match('/^(\w+)\h*=\h*(.+)/', strtolower(trim($dsn)), $parts)){
$conf['type'] = $parts[1];
if($conf['type'] == 'redis'){
list($conf['host'], $conf['port'], $conf['db']) = explode(':', $parts[2]) + [1 => 6379, 2 => null];
}elseif($conf['type'] == 'folder'){
$conf['folder'] = $parts[2];
}
// int cast numeric values
$conf = array_map(function($val){
return is_numeric($val) ? intval($val) : $val;
}, $conf);
}
return $matches;
}
/**
* check if a given DateTime() is within downTime range: downtime + 10m
* -> can be used for prevent logging errors during downTime

View File

@@ -198,7 +198,7 @@ abstract class AbstractLog implements LogInterface {
* @param \stdClass|null $handlerParams
* @return LogInterface
*/
public function addHandler(string $handlerKey, string $formatterKey = null, \stdClass $handlerParams = null): LogInterface{
public function addHandler(string $handlerKey, string $formatterKey = null, \stdClass $handlerParams = null): LogInterface {
if(!$this->hasHandlerKey($handlerKey)){
$this->handlerConfig[$handlerKey] = $formatterKey;
// add more configuration params for the new handler
@@ -214,7 +214,7 @@ abstract class AbstractLog implements LogInterface {
* @param string $handlerKey
* @return LogInterface
*/
public function addHandlerGroup(string $handlerKey): LogInterface{
public function addHandlerGroup(string $handlerKey) : LogInterface {
if(
$this->hasHandlerKey($handlerKey) &&
!$this->hasHandlerGroupKey($handlerKey)
@@ -227,7 +227,7 @@ abstract class AbstractLog implements LogInterface {
/**
* @return array
*/
public function getHandlerConfig(): array{
public function getHandlerConfig() : array{
return $this->handlerConfig;
}
@@ -237,7 +237,7 @@ abstract class AbstractLog implements LogInterface {
* @return array
* @throws \Exception
*/
public function getHandlerParams(string $handlerKey): array{
public function getHandlerParams(string $handlerKey) : array {
$params = [];
if($this->hasHandlerKey($handlerKey)){
@@ -267,14 +267,14 @@ abstract class AbstractLog implements LogInterface {
/**
* @return array
*/
public function getHandlerParamsConfig(): array{
public function getHandlerParamsConfig(): array {
return $this->handlerParamsConfig;
}
/**
* @return array
*/
public function getProcessorConfig(): array{
public function getProcessorConfig(): array {
return $this->processorConfig;
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus 4D
* Date: 01.01.2019
* Time: 16:42
*/
namespace lib\logging;
class ApiLog extends AbstractLog {
/**
* List of possible handlers (tested)
* -> final handler will be set dynamic for per instance
* @var array
*/
protected $handlerConfig = [
//'stream' => 'json'
];
protected $channelType = 'api';
public function __construct(string $action, string $level){
parent::__construct($action);
$this->setLevel($level);
}
/**
* overwrites parent
* -> we need unique channelNames for different $actions within same $channelType
* -> otherwise logs would be bundled into the first log file handler
* @return string
*/
public function getChannelName(): string{
return $this->getChannelType() . '_' . $this->getAction();
}
}

View File

@@ -15,6 +15,8 @@ interface LogInterface {
public function setLevel(string $level);
public function setTag(string $tag);
public function setData(array $data): LogInterface;
public function setTempData(array $data): LogInterface;
@@ -61,4 +63,5 @@ interface LogInterface {
public function removeHandlerGroup(string $handlerKey);
public function buffer();
}

View File

@@ -72,7 +72,7 @@ class RallyLog extends AbstractCharacterLog{
}
// add human readable changes to string ---------------------------------------------------
$data['formatted'] =$this->formatData($data);
$data['formatted'] = $this->formatData($data);
return $data;
}
@@ -89,10 +89,10 @@ class RallyLog extends AbstractCharacterLog{
!empty($data['channel'])
){
$replace = [
'{objName}' => $data['object']['objName'],
'{objId}' => $data['object']['objId'],
'{objName}' => $data['object']['objName'],
'{objId}' => $data['object']['objId'],
'{channelName}' => $data['channel']['channelName'],
'{channelId}' => $data['channel']['channelId']
'{channelId}' => $data['channel']['channelId']
];
$string = str_replace(array_keys($replace), array_values($replace), $this->getMessage());
}

View File

@@ -130,4 +130,54 @@ class Util {
sort($scopes);
return md5(serialize($scopes));
}
/**
* get some information about a $source file/dir
* @param string|null $source
* @return array
*/
static function filesystemInfo(?string $source) : array {
$info = [];
if(is_dir($source)){
$info['isDir'] = true;
}elseif(is_file($source)){
$info['isFile'] = true;
}
if(!empty($info)){
$info['chmod'] = substr(sprintf('%o', fileperms($source)), -4);
}
return $info;
}
/**
* round DateTime to interval
* @param \DateTime $dateTime
* @param string $type
* @param int $interval
* @param string $round
*/
static function roundToInterval(\DateTime &$dateTime, string $type = 'sec', int $interval = 5, string $round = 'floor'){
$hours = $minutes = $seconds = 0;
$roundInterval = function(string $format, int $interval, string $round) : int {
return call_user_func($round, $format / $interval) * $interval;
};
switch($type){
case 'hour':
$hours = $roundInterval($dateTime->format('H'), $interval, $round);
break;
case 'min':
$hours = $dateTime->format('H');
$minutes = $roundInterval($dateTime->format('i'), $interval, $round);
break;
case 'sec':
$hours = $dateTime->format('H');
$minutes = $dateTime->format('i');
$seconds = $roundInterval($dateTime->format('s'), $interval, $round);
break;
}
$dateTime->setTime($hours, $minutes, $seconds);
}
}

View File

@@ -1,245 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: Exodus
* Date: 12.03.2016
* Time: 12:28
*/
namespace lib;
use controller\LogController;
class Web extends \Web {
const ERROR_STATUS_LOG = 'HTTP %s: \'%s\' | url: %s \'%s\'%s';
/**
* max retry attempts (cURL requests) for a single resource until giving up...
* -> this is because SSO API is not very stable
* 2 == (initial request + 2 retry == max of 3)
*/
const RETRY_COUNT_MAX = 2;
/**
* end of line
* @var string
*/
private $eol = "\r\n";
/**
* get status code from Header data array
* @param array $headers
* @return int
*/
protected function getStatusCodeFromHeaders(array $headers = []) : int {
$statusCode = 0;
if(
preg_match(
'/HTTP\/1\.\d (\d{3}?)/',
implode($this->eol, $headers),
$matches
)
){
$statusCode = (int)$matches[1];
}
return $statusCode;
}
/**
* get cache time in seconds from Header data array
* @param array $headers
* @return int
*/
protected function getCacheTimeFromHeaders(array $headers = []) : int {
$cacheTime = 0;
if(
preg_match(
'/Cache-Control:(.*?)max-age=([0-9]+)/',
implode($this->eol, $headers),
$matches
)
){
$cacheTime = (int)$matches[2];
}elseif(
preg_match(
'/Access-Control-Max-Age: ([0-9]+)/',
implode($this->eol, $headers),
$matches
)
){
$cacheTime = (int)$matches[1];
}
return $cacheTime;
}
/**
* get a unique cache kay for a request
* @param $url
* @param null $options
* @return string
*/
protected function getCacheKey(string $url, $options = null) : string {
$f3 = \Base::instance();
$headers = isset($options['header']) ? implode($this->eol, (array)$options['header']) : '';
return $f3->hash(
$options['method'] . ' '
. $url . ' '
. $headers
) . 'url';
}
/**
* perform curl() request
* -> caches response by returned HTTP Cache header data
* @param string $url
* @param array|null $options
* @param array $additionalOptions
* @param int $retryCount request counter for failed call
* @return array|FALSE|mixed
*/
public function request($url, array $options = null, array $additionalOptions = [], int $retryCount = 0){
$f3 = \Base::instance();
if( !$f3->exists($hash = $this->getCacheKey($url, $options), $result) ){
// set max retry count in case of request error
$retryCountMax = isset($additionalOptions['retryCountMax']) ? (int)$additionalOptions['retryCountMax'] : self::RETRY_COUNT_MAX;
// retry same request until retry limit is reached
$retry = false;
$result = parent::request($url, $options);
$result['timeout'] = false;
$statusCode = $this->getStatusCodeFromHeaders( $result['headers'] );
switch($statusCode){
case 100:
case 200:
// request succeeded -> check if response should be cached
$ttl = $this->getCacheTimeFromHeaders( $result['headers'] );
if(
$ttl > 0 &&
!empty( json_decode( $result['body'], true ) )
){
$f3->set($hash, $result, $ttl);
}
break;
case 401:
case 415:
// unauthorized
$errorMsg = $this->getErrorMessageFromJsonResponse(
$statusCode,
$options['method'],
$url,
json_decode($result['body'])
);
// if request not within downTime time range -> log error
if( !Config::inDownTimeRange() ){
LogController::getLogger('ERROR')->write($errorMsg);
}
break;
case 500:
case 501:
case 502:
case 503:
case 505:
$retry = true;
if($retryCount == $retryCountMax){
$errorMsg = $this->getErrorMessageFromJsonResponse(
$statusCode,
$options['method'],
$url,
json_decode($result['body'])
);
// if request not within downTime time range -> log error
if( !Config::inDownTimeRange() ){
LogController::getLogger('ERROR')->write($errorMsg);
}
// trigger error
if($additionalOptions['suppressHTTPErrors'] !== true){
$f3->error($statusCode, $errorMsg);
}
}
break;
case 504:
case 0:
$retry = true;
if($retryCount == $retryCountMax){
// timeout -> response should not be cached
$result['timeout'] = true;
$errorMsg = $this->getErrorMessageFromJsonResponse(
504,
$options['method'],
$url,
json_decode($result['body'])
);
// if request not within downTime time range -> log error
if( !Config::inDownTimeRange() ){
LogController::getLogger('ERROR')->write($errorMsg);
}
if($additionalOptions['suppressHTTPErrors'] !== true){
$f3->error(504, $errorMsg);
}
}
break;
default:
// unknown status
$errorMsg = $this->getErrorMessageFromJsonResponse(
$statusCode,
$options['method'],
$url
);
if( !Config::inDownTimeRange() ){
LogController::getLogger('ERROR')->write($errorMsg);
}
break;
}
if(
$retry &&
$retryCount < $retryCountMax
){
$retryCount++;
$this->request($url, $options, $additionalOptions, $retryCount);
}
}
return $result;
}
/**
* get error message from response object
* @param int $code
* @param string $method
* @param string $url
* @param null|\stdClass $responseBody
* @return string
*/
protected function getErrorMessageFromJsonResponse($code, $method, $url, $responseBody = null){
if( empty($responseBody->message) ){
$message = @constant('Base::HTTP_' . $code);
}else{
$message = $responseBody->message;
}
$body = '';
if( !is_null($responseBody) ){
$body = ' | body: ' . print_r($responseBody, true);
}
return sprintf(self::ERROR_STATUS_LOG, $code, $message, $method, $url, $body);
}
}

View File

@@ -141,7 +141,7 @@ class AllianceModel extends BasicModel {
$alliance = parent::getById($id, $ttl, $isActive);
if($alliance->isOutdated()){
// request alliance data
$allianceData = self::getF3()->ccpClient->getAllianceData($id);
$allianceData = self::getF3()->ccpClient()->getAllianceData($id);
if( !empty($allianceData) ){
$alliance->copyfrom($allianceData, ['id', 'name', 'ticker']);
$alliance->save();

View File

@@ -11,7 +11,6 @@ namespace Model;
use Controller\Ccp\Sso as Sso;
use Controller\Api\User as User;
use DB\SQL\Schema;
use lib\Util;
use lib\Config;
use lib\Socket;
use Model\Universe;
@@ -512,15 +511,8 @@ class CharacterModel extends BasicModel {
$refreshToken &&
!empty($this->esiRefreshToken)
){
$additionalOptions = [];
if($accessToken){
// ... close to expire token exists -> moderate failover settings
$additionalOptions['suppressHTTPErrors'] = true;
$additionalOptions['retryCountMax'] = 0;
}
$ssoController = new Sso();
$accessData = $ssoController->refreshAccessToken($this->esiRefreshToken, $additionalOptions);
$accessData = $ssoController->refreshAccessToken($this->esiRefreshToken);
if(isset($accessData->accessToken, $accessData->esiAccessTokenExpires, $accessData->refreshToken)){
$this->esiAccessToken = $accessData->accessToken;
@@ -758,12 +750,12 @@ class CharacterModel extends BasicModel {
){
// Try to pull data from API
if( $accessToken = $this->getAccessToken() ){
$onlineData = self::getF3()->ccpClient->getCharacterOnlineData($this->_id, $accessToken, $additionalOptions);
$onlineData = self::getF3()->ccpClient()->getCharacterOnlineData($this->_id, $accessToken);
// check whether character is currently ingame online
if(is_bool($onlineData['online'])){
if($onlineData['online'] === true){
$locationData = self::getF3()->ccpClient->getCharacterLocationData($this->_id, $accessToken, $additionalOptions);
$locationData = self::getF3()->ccpClient()->getCharacterLocationData($this->_id, $accessToken);
if( !empty($locationData['system']['id']) ){
// character is currently in-game
@@ -807,7 +799,7 @@ class CharacterModel extends BasicModel {
// get "more" data for systemId and/or stationId -----------------------------------------
if( !empty($lookupUniverseIds) ){
// get "more" information for some Ids (e.g. name)
$universeData = self::getF3()->ccpClient->getUniverseNamesData($lookupUniverseIds, $additionalOptions);
$universeData = self::getF3()->ccpClient()->getUniverseNamesData($lookupUniverseIds);
if( !empty($universeData) && !isset($universeData['error']) ){
// We expect max ONE system AND/OR station data, not an array of e.g. systems
@@ -860,7 +852,7 @@ class CharacterModel extends BasicModel {
// check ship data for changes ------------------------------------------------------------
if( !$deleteLog ){
$shipData = self::getF3()->ccpClient->getCharacterShipData($this->_id, $accessToken, $additionalOptions);
$shipData = self::getF3()->ccpClient()->getCharacterShipData($this->_id, $accessToken);
// IDs for "shipTypeId" that require more data
$lookupShipTypeId = 0;
@@ -989,14 +981,14 @@ class CharacterModel extends BasicModel {
// -> the "id" check is just for security and should NEVER fail!
$ssoController = new Sso();
if(
!is_null( $verificationCharacterData = $ssoController->verifyCharacterData($accessToken) ) &&
$verificationCharacterData->CharacterID === $this->_id
!empty( $verificationCharacterData = $ssoController->verifyCharacterData($accessToken) ) &&
$verificationCharacterData['characterId'] === $this->_id
){
// get character data from API
$characterData = $ssoController->getCharacterData($this->_id);
if( !empty($characterData->character) ){
$characterData->character['ownerHash'] = $verificationCharacterData->CharacterOwnerHash;
$characterData->character['esiScopes'] = Util::convertScopesString($verificationCharacterData->Scopes);
$characterData->character['ownerHash'] = $verificationCharacterData['characterOwnerHash'];
$characterData->character['esiScopes'] = $verificationCharacterData['scopes'];
$this->copyfrom($characterData->character, ['ownerHash', 'esiScopes', 'securityStatus']);
$this->corporationId = $characterData->corporation;

View File

@@ -284,7 +284,7 @@ class CorporationModel extends BasicModel {
!empty($accessToken) &&
!$this->isNPC
){
$response = self::getF3()->ccpClient->getCorporationRoles($this->_id, $accessToken);
$response = self::getF3()->ccpClient()->getCorporationRoles($this->_id, $accessToken);
if( !empty($response['roles']) ){
$characterRolesData = (array)$response['roles'];
}
@@ -351,10 +351,10 @@ class CorporationModel extends BasicModel {
$corporation = parent::getById($id, $ttl, $isActive);
if($corporation->isOutdated()){
// request corporation data
$corporationData = self::getF3()->ccpClient->getCorporationData($id);
$corporationData = self::getF3()->ccpClient()->getCorporationData($id);
if( !empty($corporationData) ){
// check for NPC corporation
$corporationData['isNPC'] = self::getF3()->ccpClient->isNpcCorporation($id);
$corporationData['isNPC'] = self::getF3()->ccpClient()->isNpcCorporation($id);
$corporation->copyfrom($corporationData, ['id', 'name', 'ticker', 'memberCount', 'isNPC']);
$corporation->save();

View File

@@ -1020,7 +1020,7 @@ class MapModel extends AbstractMapTrackingModel {
* get object relevant data for model log channel
* @return array
*/
public function getLogChannelData() : array{
public function getLogChannelData() : array {
return [
'channelId' => $this->_id,
'channelName' => $this->name
@@ -1030,7 +1030,7 @@ class MapModel extends AbstractMapTrackingModel {
* get object relevant data for model log object
* @return array
*/
public function getLogObjectData() : array{
public function getLogObjectData() : array {
return [
'objId' => $this->_id,
'objName' => $this->name

View File

@@ -665,7 +665,7 @@ class SystemModel extends AbstractMapTrackingModel {
public function sendRallyPoke(array $rallyData, CharacterModel $characterModel){
// rally log needs at least one handler to be valid
$isValidLog = false;
$log = new Logging\RallyLog('rallySet', $this->getMap()->getLogChannelData());
$log = new logging\RallyLog('rallySet', $this->getMap()->getLogChannelData());
// Slack poke -----------------------------------------------------------------------------
$slackChannelKey = 'slackChannelRally';

View File

@@ -109,7 +109,7 @@ class CategoryModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseCategoryData($id);
$data = self::getF3()->ccpClient()->getUniverseCategoryData($id);
if(!empty($data)){
$this->copyfrom($data, ['id', 'name', 'published']);
$this->save();
@@ -125,7 +125,7 @@ class CategoryModel extends BasicUniverseModel {
public function loadGroupsData(int $offset = 0, int $length = 0) : array {
$groupIds = [];
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseCategoryData($this->_id);
$data = self::getF3()->ccpClient()->getUniverseCategoryData($this->_id);
if(!empty($data)){
array_multisort($data['groups'], SORT_ASC, SORT_NUMERIC);
if($length){

View File

@@ -86,7 +86,7 @@ class ConstellationModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseConstellationData($id);
$data = self::getF3()->ccpClient()->getUniverseConstellationData($id);
if(!empty($data)){
/**
* @var $region RegionModel
@@ -105,7 +105,7 @@ class ConstellationModel extends BasicUniverseModel {
*/
public function loadSystemsData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseConstellationData($this->_id);
$data = self::getF3()->ccpClient()->getUniverseConstellationData($this->_id);
if(!empty($data)){
foreach((array)$data['systems'] as $systemId){
/**

View File

@@ -111,7 +111,7 @@ class GroupModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseGroupData($id);
$data = self::getF3()->ccpClient()->getUniverseGroupData($id);
if(!empty($data)){
/**
* @var $category CategoryModel
@@ -132,7 +132,7 @@ class GroupModel extends BasicUniverseModel {
public function loadTypesData(){
$count = 0;
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseGroupData($this->_id);
$data = self::getF3()->ccpClient()->getUniverseGroupData($this->_id);
if(!empty($data)){
foreach((array)$data['types'] as $typeId){
/**

View File

@@ -96,7 +96,7 @@ class PlanetModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniversePlanetData($id);
$data = self::getF3()->ccpClient()->getUniversePlanetData($id);
if(!empty($data)){
/**
* @var $system SystemModel

View File

@@ -46,7 +46,7 @@ class RegionModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseRegionData($id);
$data = self::getF3()->ccpClient()->getUniverseRegionData($id);
if(!empty($data)){
$this->copyfrom($data, ['id', 'name', 'description']);
$this->save();
@@ -58,7 +58,7 @@ class RegionModel extends BasicUniverseModel {
*/
public function loadConstellationsData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseRegionData($this->_id);
$data = self::getF3()->ccpClient()->getUniverseRegionData($this->_id);
if(!empty($data)){
foreach((array)$data['constellations'] as $constellationsId){
/**

View File

@@ -104,7 +104,7 @@ class StargateModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseStargateData($id);
$data = self::getF3()->ccpClient()->getUniverseStargateData($id);
if(!empty($data)){

View File

@@ -80,7 +80,7 @@ class StarModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseStarData($id);
$data = self::getF3()->ccpClient()->getUniverseStarData($id);
if(!empty($data)){
/**
* @var $type TypeModel

View File

@@ -77,7 +77,7 @@ class StructureModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseStructureData($id, $accessToken, $additionalOptions);
$data = self::getF3()->ccpClient()->getUniverseStructureData($id, $accessToken);
if(!empty($data)){
/**
* @var $type TypeModel

View File

@@ -333,7 +333,7 @@ class SystemModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseSystemData($id);
$data = self::getF3()->ccpClient()->getUniverseSystemData($id);
if(!empty($data)){
/**
@@ -363,7 +363,7 @@ class SystemModel extends BasicUniverseModel {
*/
public function loadPlanetsData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseSystemData($this->_id);
$data = self::getF3()->ccpClient()->getUniverseSystemData($this->_id);
if($data['planets']){
// planets are optional since ESI v4 (e.g. Abyssal systems)
foreach((array)$data['planets'] as $planetData){
@@ -384,7 +384,7 @@ class SystemModel extends BasicUniverseModel {
*/
public function loadStargatesData(){
if( !$this->dry() ){
$data = self::getF3()->ccpClient->getUniverseSystemData($this->_id);
$data = self::getF3()->ccpClient()->getUniverseSystemData($this->_id);
if(!empty($data)){
foreach((array)$data['stargates'] as $stargateId){
/**

View File

@@ -136,7 +136,7 @@ class TypeModel extends BasicUniverseModel {
* @param array $additionalOptions
*/
protected function loadData(int $id, string $accessToken = '', array $additionalOptions = []){
$data = self::getF3()->ccpClient->getUniverseTypesData($id, $additionalOptions);
$data = self::getF3()->ccpClient()->getUniverseTypesData($id);
if(!empty($data)){
$group = $this->rel('groupId');
$group->loadById($data['groupId'], $accessToken, $additionalOptions);

View File

@@ -13,8 +13,8 @@ NAME = Pathfinder
; Version is used for CSS/JS cache busting and is part of the URL for static resources:
; e.g. public/js/vX.X.X/app.js
; Syntax: String (current version)
; Default: v1.4.4
VERSION = v1.4.4
; Default: v1.5.0
VERSION = v1.5.0
; Contact information [optional]
; Shown on 'licence', 'contact' page.
@@ -229,6 +229,7 @@ RALLY_SET =
; =================================================================================================
[PATHFINDER.TIMER]
; Login time for characters. Users get logged out after X minutes
; Hint: Set to 0 disables login time and characters stay logged in until Cookie data expires
; Syntax: Integer (minutes)
; Default: 480
LOGGED = 480

View File

@@ -9,7 +9,7 @@ APACHE.VERSION = 2.5
NGINX.VERSION = 1.9
[REQUIREMENTS.PHP]
VERSION = 7.0
VERSION = 7.1
; 64-bit version of PHP (4 = 32-bit, 8 = 64-bit)
PHP_INT_SIZE = 8

View File

@@ -20,7 +20,7 @@
"url": "../pathfinder_esi"
}],
"require": {
"php-64bit": ">=7.0",
"php-64bit": ">=7.1",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-curl": "*",
@@ -33,6 +33,14 @@
"websoftwares/monolog-zmq-handler": "0.2.*",
"swiftmailer/swiftmailer": "^6.0",
"league/html-to-markdown": "4.8.*",
"cache/redis-adapter": "1.0.*",
"cache/filesystem-adapter": "1.0.*",
"cache/array-adapter": "1.0.*",
"cache/void-adapter": "1.0.*",
"cache/namespaced-cache": "1.0.*",
"exodus4d/pathfinder_esi": "dev-develop as 0.0.x-dev"
},
"suggest": {
"ext-redis": "Redis can be used as cache backend."
}
}

View File

@@ -20,7 +20,7 @@
"url": "https://github.com/exodus4d/pathfinder_esi"
}],
"require": {
"php-64bit": ">=7.0",
"php-64bit": ">=7.1",
"ext-pdo": "*",
"ext-openssl": "*",
"ext-curl": "*",
@@ -33,6 +33,14 @@
"websoftwares/monolog-zmq-handler": "0.2.*",
"swiftmailer/swiftmailer": "^6.0",
"league/html-to-markdown": "4.8.*",
"exodus4d/pathfinder_esi": "dev-master#v1.2.5"
"cache/redis-adapter": "1.0.*",
"cache/filesystem-adapter": "1.0.*",
"cache/array-adapter": "1.0.*",
"cache/void-adapter": "1.0.*",
"cache/namespaced-cache": "1.0.*",
"exodus4d/pathfinder_esi": "dev-master#v1.3.0"
},
"suggest": {
"ext-redis": "Redis can be used as cache backend."
}
}

View File

@@ -14,9 +14,6 @@ $f3->config('app/config.ini', true);
// load environment dependent config
lib\Config::instance($f3);
// initiate CCP API Client (ESI)
lib\CcpClient::instance($f3);
// initiate cron-jobs
Cron::instance();

View File

@@ -486,6 +486,36 @@ define(['jquery'], ($) => {
7: 'Q003 - 0.0',
8: 'A009 - C13'
},
10: { // High Sec
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0',
8: 'A009 - C13'
},
11: { // Low Sec
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0',
8: 'A009 - C13'
},
12: { // 0.0
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0',
8: 'A009 - C13'
},
13: { // Shattered Wormholes (some of them are static)
1: 'E004 - C1',
2: 'L005 - C2',

View File

@@ -17,7 +17,8 @@ define([
'dialog/notification',
'dialog/manual',
'dialog/changelog',
'dialog/credit'
'dialog/credit',
'dialog/api_status',
], ($, Init, Util, Render, Gallery, bootbox) => {
'use strict';
@@ -70,6 +71,8 @@ define([
stickyPanelServerId: 'pf-landing-server-panel', // id for EVE Online server status panel
stickyPanelAdminId: 'pf-landing-admin-panel', // id for admin login panel
apiStatusTriggerClass: 'pf-api-status-trigger', // class for "api status" dialog trigger elements
// animation
animateElementClass: 'pf-animate-on-visible', // class for elements that will be animated to show
@@ -463,30 +466,40 @@ define([
dataType: 'json'
}).done(function(responseData, textStatus, request){
if(responseData.hasOwnProperty('status')){
let data = responseData.status;
data.stickyPanelServerId = config.stickyPanelServerId;
data.stickyPanelClass = config.stickyPanelClass;
let statusClass = '';
switch(data.serviceStatus.toLowerCase()){
case 'online': statusClass = 'txt-color-green'; break;
case 'vip': statusClass = 'txt-color-orange'; break;
case 'offline': statusClass = 'txt-color-redDarker'; break;
let data = {
stickyPanelServerId: config.stickyPanelServerId,
stickyPanelClass: config.stickyPanelClass,
apiStatusTriggerClass: config.apiStatusTriggerClass,
server: responseData.server,
api: responseData.api,
statusFormat: () => {
return (val, render) => {
switch(render(val)){
case 'online':
case 'green': return 'txt-color-green';
case 'vip':
case 'yellow': return 'txt-color-orange';
case 'offline':
case 'red': return 'txt-color-red';
default: return '';
}
};
}
data.serviceStatus = {
eve: data.serviceStatus,
style: statusClass
};
};
requirejs(['text!templates/ui/server_panel.html', 'mustache'], function(template, Mustache){
let content = Mustache.render(template, data);
$('#' + config.headerId).prepend(content);
$('#' + config.stickyPanelServerId).velocity('transition.slideLeftBigIn', {
duration: 240
});
requirejs(['text!templates/ui/server_panel.html', 'mustache'], function(template, Mustache){
let content = Mustache.render(template, data);
$('#' + config.headerId).prepend(content);
let stickyPanelServer = $('#' + config.stickyPanelServerId);
stickyPanelServer.velocity('transition.slideLeftBigIn', {
duration: 240
});
}
// set observer for api status dialog
stickyPanelServer.on('click', '.' + config.apiStatusTriggerClass, function(){
$.fn.apiStatusDialog(data.api);
});
});
}).fail(handleAjaxErrorResponse);
};

View File

@@ -0,0 +1,76 @@
/**
* changelog dialog (GitHub API repository information)
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox'
], ($, Init, Util, Render, bootbox) => {
'use strict';
let config = {
apiStatusDialogClass: 'pf-api-status-dialog' // class for "api status" dialog
};
/**
* show api status dialog
* @param apiData
*/
$.fn.apiStatusDialog = function(apiData){
let data = {
apiData: apiData,
methodFormat: () => {
return (val, render) => {
switch(render(val)){
case 'get': return 'txt-color-blue';
case 'post': return 'txt-color-green';
case 'put': return 'txt-color-yellow';
case 'delete': return 'txt-color-red';
default: return '';
}
};
},
statusTitle: () => {
return (val, render) => {
switch(render(val)){
case 'green': return 'ok';
case 'yellow': return 'degraded: Slow or potentially dropping requests';
case 'red': return 'bad: Most requests are not succeeding and/or are very slow (5s+) on average';
default: return 'unknown';
}
};
},
secondsFormat: () => {
return (val, render) => {
return parseFloat(render(val)).toFixed(2) + 's';
};
}
};
requirejs(['text!templates/dialog/api_status.html', 'mustache'], (template, Mustache) => {
let apiStatusDialog = bootbox.dialog({
className: config.apiStatusDialogClass,
title: 'API status',
message: Mustache.render(template, data),
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
}
}
});
apiStatusDialog.initTooltips();
// show dialog
apiStatusDialog.modal('show');
});
};
});

View File

@@ -88,7 +88,7 @@ define([
let data = {
isFirst: (i === 0),
isOdd: (i % 2 !== 0),
releaseDate: releaseData.published_at.substr(0, 10),
releaseDate: releaseData.publishedAt.substr(0, 10),
releaseData: releaseData
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -486,6 +486,36 @@ define(['jquery'], ($) => {
7: 'Q003 - 0.0',
8: 'A009 - C13'
},
10: { // High Sec
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0',
8: 'A009 - C13'
},
11: { // Low Sec
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0',
8: 'A009 - C13'
},
12: { // 0.0
1: 'E004 - C1',
2: 'L005 - C2',
3: 'Z006 - C3',
4: 'M001 - C4',
5: 'C008 - C5',
6: 'G008 - C6',
7: 'Q003 - 0.0',
8: 'A009 - C13'
},
13: { // Shattered Wormholes (some of them are static)
1: 'E004 - C1',
2: 'L005 - C2',

View File

@@ -17,7 +17,8 @@ define([
'dialog/notification',
'dialog/manual',
'dialog/changelog',
'dialog/credit'
'dialog/credit',
'dialog/api_status',
], ($, Init, Util, Render, Gallery, bootbox) => {
'use strict';
@@ -70,6 +71,8 @@ define([
stickyPanelServerId: 'pf-landing-server-panel', // id for EVE Online server status panel
stickyPanelAdminId: 'pf-landing-admin-panel', // id for admin login panel
apiStatusTriggerClass: 'pf-api-status-trigger', // class for "api status" dialog trigger elements
// animation
animateElementClass: 'pf-animate-on-visible', // class for elements that will be animated to show
@@ -463,30 +466,40 @@ define([
dataType: 'json'
}).done(function(responseData, textStatus, request){
if(responseData.hasOwnProperty('status')){
let data = responseData.status;
data.stickyPanelServerId = config.stickyPanelServerId;
data.stickyPanelClass = config.stickyPanelClass;
let statusClass = '';
switch(data.serviceStatus.toLowerCase()){
case 'online': statusClass = 'txt-color-green'; break;
case 'vip': statusClass = 'txt-color-orange'; break;
case 'offline': statusClass = 'txt-color-redDarker'; break;
let data = {
stickyPanelServerId: config.stickyPanelServerId,
stickyPanelClass: config.stickyPanelClass,
apiStatusTriggerClass: config.apiStatusTriggerClass,
server: responseData.server,
api: responseData.api,
statusFormat: () => {
return (val, render) => {
switch(render(val)){
case 'online':
case 'green': return 'txt-color-green';
case 'vip':
case 'yellow': return 'txt-color-orange';
case 'offline':
case 'red': return 'txt-color-red';
default: return '';
}
};
}
data.serviceStatus = {
eve: data.serviceStatus,
style: statusClass
};
};
requirejs(['text!templates/ui/server_panel.html', 'mustache'], function(template, Mustache){
let content = Mustache.render(template, data);
$('#' + config.headerId).prepend(content);
$('#' + config.stickyPanelServerId).velocity('transition.slideLeftBigIn', {
duration: 240
});
requirejs(['text!templates/ui/server_panel.html', 'mustache'], function(template, Mustache){
let content = Mustache.render(template, data);
$('#' + config.headerId).prepend(content);
let stickyPanelServer = $('#' + config.stickyPanelServerId);
stickyPanelServer.velocity('transition.slideLeftBigIn', {
duration: 240
});
}
// set observer for api status dialog
stickyPanelServer.on('click', '.' + config.apiStatusTriggerClass, function(){
$.fn.apiStatusDialog(data.api);
});
});
}).fail(handleAjaxErrorResponse);
};

View File

@@ -0,0 +1,76 @@
/**
* changelog dialog (GitHub API repository information)
*/
define([
'jquery',
'app/init',
'app/util',
'app/render',
'bootbox'
], ($, Init, Util, Render, bootbox) => {
'use strict';
let config = {
apiStatusDialogClass: 'pf-api-status-dialog' // class for "api status" dialog
};
/**
* show api status dialog
* @param apiData
*/
$.fn.apiStatusDialog = function(apiData){
let data = {
apiData: apiData,
methodFormat: () => {
return (val, render) => {
switch(render(val)){
case 'get': return 'txt-color-blue';
case 'post': return 'txt-color-green';
case 'put': return 'txt-color-yellow';
case 'delete': return 'txt-color-red';
default: return '';
}
};
},
statusTitle: () => {
return (val, render) => {
switch(render(val)){
case 'green': return 'ok';
case 'yellow': return 'degraded: Slow or potentially dropping requests';
case 'red': return 'bad: Most requests are not succeeding and/or are very slow (5s+) on average';
default: return 'unknown';
}
};
},
secondsFormat: () => {
return (val, render) => {
return parseFloat(render(val)).toFixed(2) + 's';
};
}
};
requirejs(['text!templates/dialog/api_status.html', 'mustache'], (template, Mustache) => {
let apiStatusDialog = bootbox.dialog({
className: config.apiStatusDialogClass,
title: 'API status',
message: Mustache.render(template, data),
show: false,
buttons: {
close: {
label: 'cancel',
className: 'btn-default'
}
}
});
apiStatusDialog.initTooltips();
// show dialog
apiStatusDialog.modal('show');
});
};
});

View File

@@ -88,7 +88,7 @@ define([
let data = {
isFirst: (i === 0),
isOdd: (i % 2 !== 0),
releaseDate: releaseData.published_at.substr(0, 10),
releaseDate: releaseData.publishedAt.substr(0, 10),
releaseData: releaseData
};

Some files were not shown because too many files have changed in this diff Show More