feat: add logs page with filtering and data table
- Implemented a new logs page with filters for level, source, and date range. - Added a data table to display logs with pagination and sorting capabilities. - Created backend functionality to fetch logs data based on filters. - Introduced a new Logs class for handling log data operations. - Added a new database migration for the logs table. - Enhanced UI with custom checkbox styles for better user experience. - Updated navigation to include a link to the logs page.
This commit is contained in:
@@ -21,7 +21,8 @@
|
|||||||
"Bash(python3:*)",
|
"Bash(python3:*)",
|
||||||
"Bash(py --version)",
|
"Bash(py --version)",
|
||||||
"Bash(where:*)",
|
"Bash(where:*)",
|
||||||
"Bash(python:*)"
|
"Bash(python:*)",
|
||||||
|
"Bash(ls -la \"c:\\\\visual studio code\\\\projekty\\\\adsPRO\\\\docs\"\" 2>/dev/null || echo \"docs dir not found \")"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
258
.vscode/ftp-kr.sync.cache.json
vendored
258
.vscode/ftp-kr.sync.cache.json
vendored
@@ -20,6 +20,54 @@
|
|||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
"class.Cache.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1006,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.Chunk.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 7304,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.Cron.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 8901,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.DbModel.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1392,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.Excel.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 4319,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.Html.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 2105,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.S.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 8418,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.Tpl.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1839,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
"controls": {
|
"controls": {
|
||||||
"class.Allegro.php": {
|
"class.Allegro.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -29,14 +77,20 @@
|
|||||||
},
|
},
|
||||||
"class.Api.php": {
|
"class.Api.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 19358,
|
"size": 19626,
|
||||||
"lmtime": 1744498273470,
|
"lmtime": 1744498273470,
|
||||||
"modified": true
|
"modified": true
|
||||||
},
|
},
|
||||||
|
"class.CampaignAlerts.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 2232,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
"class.Campaigns.php": {
|
"class.Campaigns.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 5378,
|
"size": 6153,
|
||||||
"lmtime": 1771486264591,
|
"lmtime": 1771626580811,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"class.CampaignTerms.php": {
|
"class.CampaignTerms.php": {
|
||||||
@@ -47,21 +101,27 @@
|
|||||||
},
|
},
|
||||||
"class.Clients.php": {
|
"class.Clients.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 3143,
|
"size": 11854,
|
||||||
"lmtime": 1771494823776,
|
"lmtime": 1771619242656,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"class.Cron.php": {
|
"class.Cron.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 116612,
|
"size": 198900,
|
||||||
"lmtime": 1771511676177,
|
"lmtime": 1771619004019,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.FacebookAds.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 4162,
|
||||||
|
"lmtime": 1771619366367,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"class.Products.php": {
|
"class.Products.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 39363,
|
"size": 44261,
|
||||||
"lmtime": 1771440055487,
|
"lmtime": 1771440055487,
|
||||||
"modified": false
|
"modified": true
|
||||||
},
|
},
|
||||||
"class.Site.php": {
|
"class.Site.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -71,8 +131,8 @@
|
|||||||
},
|
},
|
||||||
"class.Users.php": {
|
"class.Users.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 13549,
|
"size": 19292,
|
||||||
"lmtime": 1771493809548,
|
"lmtime": 1771617570626,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"class.XmlFiles.php": {
|
"class.XmlFiles.php": {
|
||||||
@@ -83,10 +143,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"factory": {
|
"factory": {
|
||||||
|
"class.CampaignAlerts.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 3222,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
"class.Campaigns.php": {
|
"class.Campaigns.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 10980,
|
"size": 11156,
|
||||||
"lmtime": 1771486281723,
|
"lmtime": 1771626553779,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"class.Clients.php": {
|
"class.Clients.php": {
|
||||||
@@ -101,9 +167,15 @@
|
|||||||
"lmtime": 0,
|
"lmtime": 0,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
|
"class.FacebookAds.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 29620,
|
||||||
|
"lmtime": 1771619061605,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
"class.Products.php": {
|
"class.Products.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 27192,
|
"size": 32530,
|
||||||
"lmtime": 1771170224109,
|
"lmtime": 1771170224109,
|
||||||
"modified": true
|
"modified": true
|
||||||
},
|
},
|
||||||
@@ -127,11 +199,17 @@
|
|||||||
"lmtime": 1771198088093,
|
"lmtime": 1771198088093,
|
||||||
"modified": true
|
"modified": true
|
||||||
},
|
},
|
||||||
|
"class.FacebookAdsApi.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 11724,
|
||||||
|
"lmtime": 1771619153702,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
"class.GoogleAdsApi.php": {
|
"class.GoogleAdsApi.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 99181,
|
"size": 114140,
|
||||||
"lmtime": 1771444236566,
|
"lmtime": 1771444236566,
|
||||||
"modified": false
|
"modified": true
|
||||||
},
|
},
|
||||||
"class.OpenAiApi.php": {
|
"class.OpenAiApi.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -139,13 +217,39 @@
|
|||||||
"lmtime": 1771171891986,
|
"lmtime": 1771171891986,
|
||||||
"modified": true
|
"modified": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"view": {
|
||||||
|
"class.Clients.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 192,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.Cron.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 164,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.Site.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 649,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"class.Users.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 415,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
".claude": {
|
".claude": {
|
||||||
"settings.local.json": {
|
"settings.local.json": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 549,
|
"size": 730,
|
||||||
"lmtime": 1771493868314,
|
"lmtime": 1771617115553,
|
||||||
"modified": false
|
"modified": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -157,9 +261,9 @@
|
|||||||
},
|
},
|
||||||
"config.php": {
|
"config.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 624,
|
"size": 921,
|
||||||
"lmtime": 1771497460705,
|
"lmtime": 1771497460705,
|
||||||
"modified": false
|
"modified": true
|
||||||
},
|
},
|
||||||
"cron.php": {
|
"cron.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
@@ -405,17 +509,77 @@
|
|||||||
"lmtime": 1771489966129,
|
"lmtime": 1771489966129,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
"demo_data.sql": {
|
|
||||||
"type": "-",
|
|
||||||
"size": 22351,
|
|
||||||
"lmtime": 0,
|
|
||||||
"modified": true
|
|
||||||
},
|
|
||||||
"012_cron_sync_status.sql": {
|
"012_cron_sync_status.sql": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 1830,
|
"size": 1830,
|
||||||
"lmtime": 1771493459924,
|
"lmtime": 1771493459924,
|
||||||
"modified": false
|
"modified": false
|
||||||
|
},
|
||||||
|
"013_campaign_alerts.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 880,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"014_campaign_search_terms_history.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1343,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"015_campaign_alerts_unseen.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 337,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"016_products_model_unification.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 5857,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"017_drop_products_data.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 161,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"018_products_merchant_url_flags.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1104,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"019_campaign_alerts_product_id.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 813,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"020_facebook_ads_base.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 6195,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"021_facebook_ads_conversion_metrics.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 2479,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"demo_data.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 21146,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": true
|
||||||
|
},
|
||||||
|
"022_facebook_ads_roas_all_time.sql": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 478,
|
||||||
|
"lmtime": 1771616256503,
|
||||||
|
"modified": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"robots.txt": {
|
"robots.txt": {
|
||||||
@@ -424,6 +588,36 @@
|
|||||||
"lmtime": 1744488227849,
|
"lmtime": 1744488227849,
|
||||||
"modified": false
|
"modified": false
|
||||||
},
|
},
|
||||||
|
"temp_fb_authentication.html": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1167861,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"temp_fb_authorization.html": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1182404,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"temp_fb_get_started.html": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1160708,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"temp_fb_insights_async.html": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1190062,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
|
"temp_fb_system_users.html": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 1172574,
|
||||||
|
"lmtime": 0,
|
||||||
|
"modified": false
|
||||||
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
"products": {
|
"products": {
|
||||||
"main_view.php": {
|
"main_view.php": {
|
||||||
@@ -462,8 +656,8 @@
|
|||||||
"campaigns": {
|
"campaigns": {
|
||||||
"main_view.php": {
|
"main_view.php": {
|
||||||
"type": "-",
|
"type": "-",
|
||||||
"size": 17073,
|
"size": 20532,
|
||||||
"lmtime": 1771497969444,
|
"lmtime": 1771626632932,
|
||||||
"modified": false
|
"modified": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -496,6 +690,14 @@
|
|||||||
"lmtime": 1771494851645,
|
"lmtime": 1771494851645,
|
||||||
"modified": false
|
"modified": false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"facebook_ads": {
|
||||||
|
"main_view.php": {
|
||||||
|
"type": "-",
|
||||||
|
"size": 10785,
|
||||||
|
"lmtime": 1771619352316,
|
||||||
|
"modified": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tmp": {},
|
"tmp": {},
|
||||||
|
|||||||
@@ -2,12 +2,15 @@
|
|||||||
namespace controls;
|
namespace controls;
|
||||||
class Cron
|
class Cron
|
||||||
{
|
{
|
||||||
|
static private $current_cron_action = 'cron';
|
||||||
|
|
||||||
// Uniwersalny CRON pipeline.
|
// Uniwersalny CRON pipeline.
|
||||||
// Jedno wywolanie = jeden klient + jeden dzien: kampanie -> produkty.
|
// Jedno wywolanie = jeden klient + jeden dzien: kampanie -> produkty.
|
||||||
static public function cron_universal()
|
static public function cron_universal()
|
||||||
{
|
{
|
||||||
global $mdb;
|
global $mdb;
|
||||||
|
|
||||||
|
self::$current_cron_action = __FUNCTION__;
|
||||||
self::touch_cron_invocation( __FUNCTION__ );
|
self::touch_cron_invocation( __FUNCTION__ );
|
||||||
|
|
||||||
$clients_not_deleted_sql = self::sql_clients_not_deleted();
|
$clients_not_deleted_sql = self::sql_clients_not_deleted();
|
||||||
@@ -484,6 +487,7 @@ class Cron
|
|||||||
static public function cron_products_urls()
|
static public function cron_products_urls()
|
||||||
{
|
{
|
||||||
global $mdb, $settings;
|
global $mdb, $settings;
|
||||||
|
self::$current_cron_action = __FUNCTION__;
|
||||||
self::touch_cron_invocation( __FUNCTION__ );
|
self::touch_cron_invocation( __FUNCTION__ );
|
||||||
|
|
||||||
$api = new \services\GoogleAdsApi();
|
$api = new \services\GoogleAdsApi();
|
||||||
@@ -1091,24 +1095,6 @@ class Cron
|
|||||||
$campaigns_by_db_id[ $db_campaign_id ] = $campaign_data;
|
$campaigns_by_db_id[ $db_campaign_id ] = $campaign_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
$existing_campaign_histories = $mdb -> query(
|
|
||||||
'SELECT ch.campaign_id
|
|
||||||
FROM campaigns_history AS ch
|
|
||||||
INNER JOIN campaigns AS c ON c.id = ch.campaign_id
|
|
||||||
WHERE c.client_id = :client_id
|
|
||||||
AND ch.date_add = :date_add',
|
|
||||||
[
|
|
||||||
':client_id' => $client_id,
|
|
||||||
':date_add' => $date
|
|
||||||
]
|
|
||||||
) -> fetchAll( \PDO::FETCH_COLUMN );
|
|
||||||
|
|
||||||
$campaign_history_exists = [];
|
|
||||||
foreach ( (array) $existing_campaign_histories as $history_campaign_id )
|
|
||||||
{
|
|
||||||
$campaign_history_exists[ (int) $history_campaign_id ] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$existing_ad_groups_rows = $mdb -> query(
|
$existing_ad_groups_rows = $mdb -> query(
|
||||||
'SELECT ag.id, ag.campaign_id, ag.ad_group_id, ag.ad_group_name
|
'SELECT ag.id, ag.campaign_id, ag.ad_group_id, ag.ad_group_name
|
||||||
FROM campaign_ad_groups AS ag
|
FROM campaign_ad_groups AS ag
|
||||||
@@ -1166,7 +1152,7 @@ class Cron
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$resolve_scope_ids = function( $campaign_external_id, $campaign_name, $ad_group_external_id, $ad_group_name ) use ( &$campaigns_by_external_id, &$campaigns_by_db_id, &$campaign_history_exists, &$ad_groups_by_scope, $client_id, $date, $mdb )
|
$resolve_scope_ids = function( $campaign_external_id, $campaign_name, $ad_group_external_id, $ad_group_name ) use ( &$campaigns_by_external_id, &$campaigns_by_db_id, &$ad_groups_by_scope, $client_id, $date, $mdb )
|
||||||
{
|
{
|
||||||
$campaign_external_id = (int) $campaign_external_id;
|
$campaign_external_id = (int) $campaign_external_id;
|
||||||
$campaign_name = trim( (string) $campaign_name );
|
$campaign_name = trim( (string) $campaign_name );
|
||||||
@@ -1213,22 +1199,6 @@ class Cron
|
|||||||
|
|
||||||
$db_campaign_id = (int) ( $campaign_data['id'] ?? 0 );
|
$db_campaign_id = (int) ( $campaign_data['id'] ?? 0 );
|
||||||
|
|
||||||
if ( $db_campaign_id > 0 && !isset( $campaign_history_exists[ $db_campaign_id ] ) )
|
|
||||||
{
|
|
||||||
$mdb -> insert( 'campaigns_history', [
|
|
||||||
'campaign_id' => $db_campaign_id,
|
|
||||||
'roas_30_days' => 0,
|
|
||||||
'roas_all_time' => 0,
|
|
||||||
'budget' => 0,
|
|
||||||
'money_spent' => 0,
|
|
||||||
'conversion_value' => 0,
|
|
||||||
'bidding_strategy' => '',
|
|
||||||
'date_add' => $date
|
|
||||||
] );
|
|
||||||
|
|
||||||
$campaign_history_exists[ $db_campaign_id ] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $db_campaign_id <= 0 )
|
if ( $db_campaign_id <= 0 )
|
||||||
{
|
{
|
||||||
return [ 'campaign_id' => 0, 'ad_group_id' => 0 ];
|
return [ 'campaign_id' => 0, 'ad_group_id' => 0 ];
|
||||||
@@ -1546,23 +1516,6 @@ class Cron
|
|||||||
|
|
||||||
$db_campaign_id = (int) $mdb -> id();
|
$db_campaign_id = (int) $mdb -> id();
|
||||||
|
|
||||||
if ( $db_campaign_id > 0 && $date_sync )
|
|
||||||
{
|
|
||||||
if ( !$mdb -> count( 'campaigns_history', [ 'AND' => [ 'campaign_id' => $db_campaign_id, 'date_add' => $date_sync ] ] ) )
|
|
||||||
{
|
|
||||||
$mdb -> insert( 'campaigns_history', [
|
|
||||||
'campaign_id' => $db_campaign_id,
|
|
||||||
'roas_30_days' => 0,
|
|
||||||
'roas_all_time' => 0,
|
|
||||||
'budget' => 0,
|
|
||||||
'money_spent' => 0,
|
|
||||||
'conversion_value' => 0,
|
|
||||||
'bidding_strategy' => '',
|
|
||||||
'date_add' => $date_sync
|
|
||||||
] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $db_campaign_id;
|
return $db_campaign_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1961,211 +1914,11 @@ class Cron
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ARCHIWUM: stara wersja cron_campaigns pozostawiona do porownan i ewentualnego rollbacku.
|
|
||||||
static public function cron_campaigns_archive()
|
|
||||||
{
|
|
||||||
global $mdb, $settings;
|
|
||||||
self::touch_cron_invocation( __FUNCTION__ );
|
|
||||||
|
|
||||||
$api = new \services\GoogleAdsApi();
|
|
||||||
|
|
||||||
if ( !$api -> is_configured() )
|
|
||||||
{
|
|
||||||
echo json_encode( [ 'result' => 'Google Ads API nie jest skonfigurowane. Uzupelnij dane w Ustawieniach.' ] );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sync_date = \S::get( 'date' ) ? date( 'Y-m-d', strtotime( \S::get( 'date' ) ) ) : date( 'Y-m-d' );
|
|
||||||
$conversion_window_days = self::get_conversion_window_days();
|
|
||||||
$sync_dates = self::build_backfill_dates( $sync_date, $conversion_window_days );
|
|
||||||
$client_id = (int) \S::get( 'client_id' );
|
|
||||||
|
|
||||||
if ( $client_id > 0 )
|
|
||||||
{
|
|
||||||
$client = $mdb -> get( 'clients', '*', [ 'AND' => [
|
|
||||||
'id' => $client_id,
|
|
||||||
'google_ads_customer_id[!]' => null,
|
|
||||||
'deleted' => 0
|
|
||||||
] ] );
|
|
||||||
|
|
||||||
if ( !$client )
|
|
||||||
{
|
|
||||||
echo json_encode( [ 'result' => 'Nie znaleziono klienta z poprawnym Google Ads Customer ID.', 'client_id' => $client_id ] );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$sync = self::sync_campaigns_for_client( $client, $api, $sync_date, true );
|
|
||||||
|
|
||||||
echo json_encode( [
|
|
||||||
'result' => empty( $sync['errors'] ) ? 'Synchronizacja kampanii zakonczona.' : 'Synchronizacja kampanii zakonczona z bledami.',
|
|
||||||
'client_id' => (int) $client['id'],
|
|
||||||
'date' => $sync_date,
|
|
||||||
'active_date' => $sync_date,
|
|
||||||
'processed_records' => (int) $sync['processed_records'],
|
|
||||||
'ad_groups_synced' => (int) ( $sync['ad_groups_synced'] ?? 0 ),
|
|
||||||
'search_terms_synced' => (int) ( $sync['search_terms_synced'] ?? 0 ),
|
|
||||||
'keywords_synced' => (int) ( $sync['keywords_synced'] ?? 0 ),
|
|
||||||
'negative_keywords_synced' => (int) ( $sync['negative_keywords_synced'] ?? 0 ),
|
|
||||||
'alerts_synced' => (int) ( $sync['alerts_synced'] ?? 0 ),
|
|
||||||
'errors' => $sync['errors']
|
|
||||||
] );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::cleanup_old_sync_rows( 30 );
|
|
||||||
|
|
||||||
$client_ids = $mdb -> query( "SELECT id FROM clients WHERE deleted = 0 AND google_ads_customer_id IS NOT NULL AND google_ads_customer_id <> '' ORDER BY id ASC" ) -> fetchAll( \PDO::FETCH_COLUMN );
|
|
||||||
$client_ids = array_values( array_unique( array_map( 'intval', $client_ids ) ) );
|
|
||||||
|
|
||||||
if ( empty( $client_ids ) )
|
|
||||||
{
|
|
||||||
echo json_encode( [ 'result' => 'Brak klientow z ustawionym Google Ads Customer ID.' ] );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$clients_map = [];
|
|
||||||
$clients = $mdb -> select( 'clients', '*', [
|
|
||||||
'AND' => [
|
|
||||||
'google_ads_customer_id[!]' => null,
|
|
||||||
'deleted' => 0
|
|
||||||
],
|
|
||||||
'ORDER' => [ 'id' => 'ASC' ]
|
|
||||||
] );
|
|
||||||
foreach ( $clients as $c )
|
|
||||||
{
|
|
||||||
$clients_map[ (int) $c['id'] ] = $c;
|
|
||||||
}
|
|
||||||
|
|
||||||
self::ensure_sync_rows( 'campaigns', $sync_dates, $client_ids );
|
|
||||||
|
|
||||||
$active_client_id = self::get_active_client( 'campaigns' );
|
|
||||||
|
|
||||||
if ( !$active_client_id )
|
|
||||||
{
|
|
||||||
echo json_encode( [
|
|
||||||
'result' => 'Wszyscy klienci kampanii zostali juz przetworzeni dla calego okna dat.',
|
|
||||||
'date' => $sync_date,
|
|
||||||
'active_date' => $sync_date,
|
|
||||||
'conversion_window_days' => $conversion_window_days,
|
|
||||||
'dates_synced' => $sync_dates,
|
|
||||||
'processed_clients' => count( $client_ids ),
|
|
||||||
'total_clients' => count( $client_ids )
|
|
||||||
] );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$dates_per_run_default = (int) ( $settings['cron_campaigns_clients_per_run'] ?? 2 );
|
|
||||||
if ( $dates_per_run_default <= 0 )
|
|
||||||
{
|
|
||||||
$dates_per_run_default = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
$dates_per_run = (int) \S::get( 'clients_per_run' );
|
|
||||||
if ( $dates_per_run <= 0 )
|
|
||||||
{
|
|
||||||
$dates_per_run = $dates_per_run_default;
|
|
||||||
}
|
|
||||||
$dates_per_run = min( 20, $dates_per_run );
|
|
||||||
|
|
||||||
$dates_batch = self::get_pending_dates_for_client( 'campaigns', $active_client_id, 'pending', $dates_per_run );
|
|
||||||
|
|
||||||
if ( empty( $dates_batch ) )
|
|
||||||
{
|
|
||||||
echo json_encode( [
|
|
||||||
'result' => 'Wszystkie daty klienta przetworzone. Kolejne wywolanie przejdzie do nastepnego klienta.',
|
|
||||||
'date' => $sync_date,
|
|
||||||
'active_client_id' => $active_client_id,
|
|
||||||
'conversion_window_days' => $conversion_window_days,
|
|
||||||
'dates_synced' => $sync_dates,
|
|
||||||
'processed_clients' => count( $client_ids ),
|
|
||||||
'total_clients' => count( $client_ids )
|
|
||||||
] );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$selected_client = $clients_map[ $active_client_id ] ?? null;
|
|
||||||
if ( !$selected_client )
|
|
||||||
{
|
|
||||||
echo json_encode( [
|
|
||||||
'result' => 'Nie udalo sie znalezc klienta do synchronizacji kampanii. ID: ' . $active_client_id,
|
|
||||||
'active_client_id' => $active_client_id,
|
|
||||||
'errors' => [ 'Klient ID ' . $active_client_id . ' nie znaleziony w clients_map.' ]
|
|
||||||
] );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$dates_processed_in_call = [];
|
|
||||||
$errors = [];
|
|
||||||
$processed_records_total = 0;
|
|
||||||
$ad_groups_synced_total = 0;
|
|
||||||
$search_terms_synced_total = 0;
|
|
||||||
$keywords_synced_total = 0;
|
|
||||||
$negative_keywords_synced_total = 0;
|
|
||||||
$alerts_synced_total = 0;
|
|
||||||
|
|
||||||
foreach ( $dates_batch as $active_date )
|
|
||||||
{
|
|
||||||
$sync_details = ( $active_date === $sync_date );
|
|
||||||
|
|
||||||
$sync = self::sync_campaigns_for_client( $selected_client, $api, $active_date, $sync_details );
|
|
||||||
$processed_records_total += (int) ( $sync['processed_records'] ?? 0 );
|
|
||||||
$ad_groups_synced_total += (int) ( $sync['ad_groups_synced'] ?? 0 );
|
|
||||||
$search_terms_synced_total += (int) ( $sync['search_terms_synced'] ?? 0 );
|
|
||||||
$keywords_synced_total += (int) ( $sync['keywords_synced'] ?? 0 );
|
|
||||||
$negative_keywords_synced_total += (int) ( $sync['negative_keywords_synced'] ?? 0 );
|
|
||||||
$alerts_synced_total += (int) ( $sync['alerts_synced'] ?? 0 );
|
|
||||||
|
|
||||||
$error_msg = null;
|
|
||||||
if ( !empty( $sync['errors'] ) )
|
|
||||||
{
|
|
||||||
$errors = array_merge( $errors, (array) $sync['errors'] );
|
|
||||||
$error_msg = implode( '; ', (array) $sync['errors'] );
|
|
||||||
}
|
|
||||||
|
|
||||||
self::mark_sync_phase( 'campaigns', $active_date, $active_client_id, 'done', $error_msg );
|
|
||||||
$dates_processed_in_call[] = $active_date;
|
|
||||||
}
|
|
||||||
|
|
||||||
$done_count = (int) $mdb -> query(
|
|
||||||
"SELECT COUNT(*) FROM cron_sync_status WHERE pipeline = 'campaigns' AND client_id = :client_id AND phase = 'done'
|
|
||||||
AND sync_date >= DATE_SUB(CURDATE(), INTERVAL 90 DAY)",
|
|
||||||
[ ':client_id' => $active_client_id ]
|
|
||||||
) -> fetchColumn();
|
|
||||||
$total_count = (int) $mdb -> query(
|
|
||||||
"SELECT COUNT(*) FROM cron_sync_status WHERE pipeline = 'campaigns' AND client_id = :client_id
|
|
||||||
AND sync_date >= DATE_SUB(CURDATE(), INTERVAL 90 DAY)",
|
|
||||||
[ ':client_id' => $active_client_id ]
|
|
||||||
) -> fetchColumn();
|
|
||||||
|
|
||||||
$remaining_dates = max( 0, $total_count - $done_count );
|
|
||||||
$estimated_calls_remaining = (int) ceil( $remaining_dates / max( 1, $dates_per_run ) );
|
|
||||||
|
|
||||||
echo json_encode( [
|
|
||||||
'result' => empty( $errors ) ? 'Synchronizacja kampanii zakonczona.' : 'Synchronizacja kampanii zakonczona z bledami.',
|
|
||||||
'active_client_id' => $active_client_id,
|
|
||||||
'dates_per_run' => $dates_per_run,
|
|
||||||
'dates_processed_in_call' => count( $dates_processed_in_call ),
|
|
||||||
'processed_dates' => $dates_processed_in_call,
|
|
||||||
'remaining_dates' => $remaining_dates,
|
|
||||||
'estimated_calls_remaining' => $estimated_calls_remaining,
|
|
||||||
'date' => $sync_date,
|
|
||||||
'conversion_window_days' => $conversion_window_days,
|
|
||||||
'dates_synced' => $sync_dates,
|
|
||||||
'processed_records' => $processed_records_total,
|
|
||||||
'ad_groups_synced' => $ad_groups_synced_total,
|
|
||||||
'search_terms_synced' => $search_terms_synced_total,
|
|
||||||
'keywords_synced' => $keywords_synced_total,
|
|
||||||
'negative_keywords_synced' => $negative_keywords_synced_total,
|
|
||||||
'alerts_synced' => $alerts_synced_total,
|
|
||||||
'total_clients' => count( $client_ids ),
|
|
||||||
'errors' => $errors
|
|
||||||
] );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function cron_campaigns_product_alerts_merchant()
|
static public function cron_campaigns_product_alerts_merchant()
|
||||||
{
|
{
|
||||||
global $mdb, $settings;
|
global $mdb, $settings;
|
||||||
|
self::$current_cron_action = __FUNCTION__;
|
||||||
self::touch_cron_invocation( __FUNCTION__ );
|
self::touch_cron_invocation( __FUNCTION__ );
|
||||||
|
|
||||||
$api = new \services\GoogleAdsApi();
|
$api = new \services\GoogleAdsApi();
|
||||||
@@ -2693,284 +2446,7 @@ class Cron
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
static private function count_pending_campaign_dates_for_client( $client_id )
|
|
||||||
{
|
|
||||||
global $mdb;
|
|
||||||
|
|
||||||
return (int) $mdb -> query(
|
|
||||||
"SELECT COUNT(*)
|
|
||||||
FROM cron_sync_status
|
|
||||||
WHERE pipeline = 'campaigns'
|
|
||||||
AND client_id = :client_id
|
|
||||||
AND phase = 'pending'
|
|
||||||
AND sync_date >= DATE_SUB(CURDATE(), INTERVAL 90 DAY)",
|
|
||||||
[ ':client_id' => (int) $client_id ]
|
|
||||||
) -> fetchColumn();
|
|
||||||
}
|
|
||||||
|
|
||||||
static private function sync_campaigns_for_client( $client, $api, $as_of_date = null, $sync_details = true )
|
|
||||||
{
|
|
||||||
global $mdb;
|
|
||||||
|
|
||||||
$as_of_date = $as_of_date ? date( 'Y-m-d', strtotime( $as_of_date ) ) : date( 'Y-m-d' );
|
|
||||||
$sync_details = (bool) $sync_details;
|
|
||||||
$processed = 0;
|
|
||||||
$errors = [];
|
|
||||||
$customer_id = $client['google_ads_customer_id'];
|
|
||||||
$campaigns_db_map = [];
|
|
||||||
|
|
||||||
$campaigns_30 = $api -> get_campaigns_30_days( $customer_id, $as_of_date );
|
|
||||||
if ( $campaigns_30 === false )
|
|
||||||
{
|
|
||||||
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
||||||
$errors[] = 'Blad API dla klienta ' . $client['name'] . ' (ID: ' . $customer_id . '): ' . $last_err;
|
|
||||||
|
|
||||||
return [
|
|
||||||
'processed_records' => 0,
|
|
||||||
'ad_groups_synced' => 0,
|
|
||||||
'search_terms_synced' => 0,
|
|
||||||
'keywords_synced' => 0,
|
|
||||||
'negative_keywords_synced' => 0,
|
|
||||||
'alerts_synced' => 0,
|
|
||||||
'errors' => $errors
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !is_array( $campaigns_30 ) )
|
|
||||||
{
|
|
||||||
$campaigns_30 = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$campaigns_all_time = $api -> get_campaigns_all_time( $customer_id, $as_of_date );
|
|
||||||
if ( $campaigns_all_time === false )
|
|
||||||
{
|
|
||||||
$last_err = \services\GoogleAdsApi::get_setting( 'google_ads_last_error' );
|
|
||||||
$errors[] = 'Blad pobierania danych all time dla klienta ' . $client['name'] . ' (ID: ' . $customer_id . '): ' . $last_err;
|
|
||||||
|
|
||||||
return [
|
|
||||||
'processed_records' => 0,
|
|
||||||
'ad_groups_synced' => 0,
|
|
||||||
'search_terms_synced' => 0,
|
|
||||||
'keywords_synced' => 0,
|
|
||||||
'negative_keywords_synced' => 0,
|
|
||||||
'alerts_synced' => 0,
|
|
||||||
'errors' => $errors
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$all_time_map = [];
|
|
||||||
$all_time_totals = [
|
|
||||||
'cost' => 0.0,
|
|
||||||
'conversion_value' => 0.0,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ( is_array( $campaigns_all_time ) )
|
|
||||||
{
|
|
||||||
foreach ( $campaigns_all_time as $cat )
|
|
||||||
{
|
|
||||||
$all_time_map[ (string) ( $cat['campaign_id'] ?? '' ) ] = (float) ( $cat['roas_all_time'] ?? 0 );
|
|
||||||
$all_time_totals['cost'] += (float) ( $cat['cost_all_time'] ?? 0 );
|
|
||||||
$all_time_totals['conversion_value'] += (float) ( $cat['conversion_value_all_time'] ?? 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$account_30_totals = [
|
|
||||||
'budget' => 0.0,
|
|
||||||
'money_spent' => 0.0,
|
|
||||||
'conversion_value' => 0.0,
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ( $campaigns_30 as $campaign )
|
|
||||||
{
|
|
||||||
$external_campaign_id = isset( $campaign['campaign_id'] ) ? (string) $campaign['campaign_id'] : '';
|
|
||||||
if ( $external_campaign_id === '' )
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$advertising_channel_type = strtoupper( trim( (string) ( $campaign['advertising_channel_type'] ?? '' ) ) );
|
|
||||||
|
|
||||||
$account_30_totals['budget'] += (float) ( $campaign['budget'] ?? 0 );
|
|
||||||
$account_30_totals['money_spent'] += (float) ( $campaign['money_spent'] ?? 0 );
|
|
||||||
$account_30_totals['conversion_value'] += (float) ( $campaign['conversion_value'] ?? 0 );
|
|
||||||
|
|
||||||
if ( !$mdb -> count( 'campaigns', [ 'AND' => [
|
|
||||||
'client_id' => $client['id'],
|
|
||||||
'campaign_id' => $external_campaign_id
|
|
||||||
] ] ) )
|
|
||||||
{
|
|
||||||
$mdb -> insert( 'campaigns', [
|
|
||||||
'client_id' => $client['id'],
|
|
||||||
'campaign_id' => $external_campaign_id,
|
|
||||||
'campaign_name' => $campaign['campaign_name'],
|
|
||||||
'advertising_channel_type' => $advertising_channel_type !== '' ? $advertising_channel_type : null
|
|
||||||
] );
|
|
||||||
$db_campaign_id = $mdb -> id();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$db_campaign_id = $mdb -> get( 'campaigns', 'id', [ 'AND' => [
|
|
||||||
'client_id' => $client['id'],
|
|
||||||
'campaign_id' => $external_campaign_id
|
|
||||||
] ] );
|
|
||||||
|
|
||||||
$mdb -> update( 'campaigns', [
|
|
||||||
'campaign_name' => $campaign['campaign_name'],
|
|
||||||
'advertising_channel_type' => $advertising_channel_type !== '' ? $advertising_channel_type : null
|
|
||||||
], [ 'id' => $db_campaign_id ] );
|
|
||||||
}
|
|
||||||
|
|
||||||
$bidding_strategy = self::format_bidding_strategy(
|
|
||||||
$campaign['bidding_strategy'],
|
|
||||||
$campaign['target_roas'] ?? 0
|
|
||||||
);
|
|
||||||
|
|
||||||
$history_data = [
|
|
||||||
'roas_30_days' => $campaign['roas_30_days'],
|
|
||||||
'roas_all_time' => $all_time_map[ $external_campaign_id ] ?? 0,
|
|
||||||
'budget' => $campaign['budget'],
|
|
||||||
'money_spent' => $campaign['money_spent'],
|
|
||||||
'conversion_value' => $campaign['conversion_value'],
|
|
||||||
'bidding_strategy' => $bidding_strategy,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ( $mdb -> count( 'campaigns_history', [ 'AND' => [
|
|
||||||
'campaign_id' => $db_campaign_id,
|
|
||||||
'date_add' => $as_of_date
|
|
||||||
] ] ) )
|
|
||||||
{
|
|
||||||
$mdb -> update( 'campaigns_history', $history_data, [ 'AND' => [
|
|
||||||
'campaign_id' => $db_campaign_id,
|
|
||||||
'date_add' => $as_of_date
|
|
||||||
] ] );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$history_data['campaign_id'] = $db_campaign_id;
|
|
||||||
$history_data['date_add'] = $as_of_date;
|
|
||||||
$mdb -> insert( 'campaigns_history', $history_data );
|
|
||||||
}
|
|
||||||
|
|
||||||
$campaigns_db_map[ $external_campaign_id ] = (int) $db_campaign_id;
|
|
||||||
$processed++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$account_roas_30 = ( $account_30_totals['money_spent'] > 0 )
|
|
||||||
? round( ( $account_30_totals['conversion_value'] / $account_30_totals['money_spent'] ) * 100, 2 )
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
$account_roas_all_time = ( $all_time_totals['cost'] > 0 )
|
|
||||||
? round( ( $all_time_totals['conversion_value'] / $all_time_totals['cost'] ) * 100, 2 )
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
if ( !$mdb -> count( 'campaigns', [ 'AND' => [
|
|
||||||
'client_id' => $client['id'],
|
|
||||||
'campaign_id' => 0
|
|
||||||
] ] ) )
|
|
||||||
{
|
|
||||||
$mdb -> insert( 'campaigns', [
|
|
||||||
'client_id' => $client['id'],
|
|
||||||
'campaign_id' => 0,
|
|
||||||
'campaign_name' => '--- konto ---',
|
|
||||||
'advertising_channel_type' => null
|
|
||||||
] );
|
|
||||||
$db_account_campaign_id = $mdb -> id();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$db_account_campaign_id = $mdb -> get( 'campaigns', 'id', [ 'AND' => [
|
|
||||||
'client_id' => $client['id'],
|
|
||||||
'campaign_id' => 0
|
|
||||||
] ] );
|
|
||||||
|
|
||||||
$mdb -> update( 'campaigns', [
|
|
||||||
'campaign_name' => '--- konto ---',
|
|
||||||
'advertising_channel_type' => null
|
|
||||||
], [ 'id' => $db_account_campaign_id ] );
|
|
||||||
}
|
|
||||||
|
|
||||||
$account_history_data = [
|
|
||||||
'roas_30_days' => $account_roas_30,
|
|
||||||
'roas_all_time' => $account_roas_all_time,
|
|
||||||
'budget' => $account_30_totals['budget'],
|
|
||||||
'money_spent' => $account_30_totals['money_spent'],
|
|
||||||
'conversion_value' => $account_30_totals['conversion_value'],
|
|
||||||
'bidding_strategy' => 'Konto (agregacja wszystkich kampanii)',
|
|
||||||
];
|
|
||||||
|
|
||||||
if ( $mdb -> count( 'campaigns_history', [ 'AND' => [
|
|
||||||
'campaign_id' => $db_account_campaign_id,
|
|
||||||
'date_add' => $as_of_date
|
|
||||||
] ] ) )
|
|
||||||
{
|
|
||||||
$mdb -> update( 'campaigns_history', $account_history_data, [ 'AND' => [
|
|
||||||
'campaign_id' => $db_account_campaign_id,
|
|
||||||
'date_add' => $as_of_date
|
|
||||||
] ] );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$account_history_data['campaign_id'] = $db_account_campaign_id;
|
|
||||||
$account_history_data['date_add'] = $as_of_date;
|
|
||||||
$mdb -> insert( 'campaigns_history', $account_history_data );
|
|
||||||
}
|
|
||||||
|
|
||||||
$processed++;
|
|
||||||
|
|
||||||
if ( !$sync_details )
|
|
||||||
{
|
|
||||||
// Daty historyczne: buduj ad_group_db_map z bazy i pobierz search terms za te date
|
|
||||||
$ad_group_db_map = self::build_ad_group_db_map_from_db( $campaigns_db_map );
|
|
||||||
|
|
||||||
$search_terms_daily = self::sync_campaign_search_terms_daily( $campaigns_db_map, $ad_group_db_map, $customer_id, $api, $as_of_date );
|
|
||||||
$errors = array_merge( $errors, $search_terms_daily['errors'] );
|
|
||||||
|
|
||||||
return [
|
|
||||||
'processed_records' => $processed,
|
|
||||||
'ad_groups_synced' => 0,
|
|
||||||
'search_terms_synced' => (int) $search_terms_daily['count'],
|
|
||||||
'keywords_synced' => 0,
|
|
||||||
'negative_keywords_synced' => 0,
|
|
||||||
'alerts_synced' => 0,
|
|
||||||
'errors' => $errors
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dzisiejsza data: najpierw sync ad_groups (DELETE + INSERT), potem search terms daily ze swiezym mapem
|
|
||||||
$ad_groups_sync = self::sync_campaign_ad_groups_for_client( $campaigns_db_map, $customer_id, $api, $as_of_date );
|
|
||||||
$errors = array_merge( $errors, $ad_groups_sync['errors'] );
|
|
||||||
|
|
||||||
$search_terms_daily = self::sync_campaign_search_terms_daily( $campaigns_db_map, $ad_groups_sync['ad_group_map'], $customer_id, $api, $as_of_date );
|
|
||||||
$errors = array_merge( $errors, $search_terms_daily['errors'] );
|
|
||||||
|
|
||||||
$aggregate_count = self::aggregate_campaign_search_terms_for_client( (int) $client['id'], $as_of_date );
|
|
||||||
$keywords_sync = self::sync_campaign_keywords_for_client( $campaigns_db_map, $ad_groups_sync['ad_group_map'], $customer_id, $api, $as_of_date );
|
|
||||||
$negative_keywords_sync = self::sync_campaign_negative_keywords_for_client( $campaigns_db_map, $ad_groups_sync['ad_group_map'], $customer_id, $api, $as_of_date );
|
|
||||||
$alerts_sync = self::sync_product_campaign_alerts_for_client(
|
|
||||||
$client,
|
|
||||||
$campaigns_db_map,
|
|
||||||
$ad_groups_sync['ad_group_map'],
|
|
||||||
$customer_id,
|
|
||||||
$api,
|
|
||||||
$as_of_date,
|
|
||||||
[
|
|
||||||
'run_missing_mapping_alerts' => true,
|
|
||||||
'run_merchant_validation_alerts' => false
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
$errors = array_merge( $errors, $keywords_sync['errors'], $negative_keywords_sync['errors'], $alerts_sync['errors'] );
|
|
||||||
|
|
||||||
return [
|
|
||||||
'processed_records' => $processed,
|
|
||||||
'ad_groups_synced' => (int) $ad_groups_sync['count'],
|
|
||||||
'search_terms_synced' => (int) $search_terms_daily['count'] + $aggregate_count,
|
|
||||||
'keywords_synced' => (int) $keywords_sync['count'],
|
|
||||||
'negative_keywords_synced' => (int) $negative_keywords_sync['count'],
|
|
||||||
'alerts_synced' => (int) ( $alerts_sync['count'] ?? 0 ),
|
|
||||||
'errors' => $errors
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
static private function sync_product_campaign_alerts_for_client( $client, $campaigns_db_map, $ad_group_db_map, $customer_id, $api, $date_sync, $options = [] )
|
static private function sync_product_campaign_alerts_for_client( $client, $campaigns_db_map, $ad_group_db_map, $customer_id, $api, $date_sync, $options = [] )
|
||||||
{
|
{
|
||||||
@@ -4868,6 +4344,7 @@ class Cron
|
|||||||
|
|
||||||
static public function cron_facebook_ads()
|
static public function cron_facebook_ads()
|
||||||
{
|
{
|
||||||
|
self::$current_cron_action = __FUNCTION__;
|
||||||
self::touch_cron_invocation( __FUNCTION__ );
|
self::touch_cron_invocation( __FUNCTION__ );
|
||||||
self::output_cron_response( self::run_facebook_ads_sync_payload( (int) \S::get( 'client_id' ) ) );
|
self::output_cron_response( self::run_facebook_ads_sync_payload( (int) \S::get( 'client_id' ) ) );
|
||||||
}
|
}
|
||||||
@@ -5243,6 +4720,23 @@ class Cron
|
|||||||
{
|
{
|
||||||
$payload = is_array( $payload ) ? $payload : [ 'result' => (string) $payload ];
|
$payload = is_array( $payload ) ? $payload : [ 'result' => (string) $payload ];
|
||||||
|
|
||||||
|
// --- Logowanie do tabeli logs ---
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$has_errors = !empty( $payload['errors'] );
|
||||||
|
$log_level = $has_errors ? 'error' : 'info';
|
||||||
|
$log_source = self::$current_cron_action ?? 'cron';
|
||||||
|
$log_client_id = $payload['active_client_id'] ?? $payload['client_id'] ?? null;
|
||||||
|
$log_message = (string) ( $payload['result'] ?? 'Brak komunikatu' );
|
||||||
|
|
||||||
|
\factory\Logs::add( $log_level, $log_source, $log_message, $payload, $log_client_id );
|
||||||
|
\factory\Logs::cleanup_old( 30 );
|
||||||
|
}
|
||||||
|
catch ( \Throwable $e )
|
||||||
|
{
|
||||||
|
$payload['_log_error'] = $e -> getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
if ( self::is_debug_requested() )
|
if ( self::is_debug_requested() )
|
||||||
{
|
{
|
||||||
header( 'Content-Type: text/html; charset=utf-8' );
|
header( 'Content-Type: text/html; charset=utf-8' );
|
||||||
|
|||||||
151
autoload/controls/class.Logs.php
Normal file
151
autoload/controls/class.Logs.php
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
namespace controls;
|
||||||
|
|
||||||
|
class Logs
|
||||||
|
{
|
||||||
|
static public function main_view()
|
||||||
|
{
|
||||||
|
$sources = \factory\Logs::get_sources();
|
||||||
|
|
||||||
|
return \view\Logs::main_view( $sources );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_logs_data_table()
|
||||||
|
{
|
||||||
|
$start = (int) \S::get( 'start' );
|
||||||
|
$length = (int) \S::get( 'length' );
|
||||||
|
$draw = (int) \S::get( 'draw' );
|
||||||
|
|
||||||
|
$filters = [];
|
||||||
|
|
||||||
|
$level = trim( (string) \S::get( 'level' ) );
|
||||||
|
if ( $level !== '' && $level !== 'all' )
|
||||||
|
{
|
||||||
|
$filters['level'] = $level;
|
||||||
|
}
|
||||||
|
|
||||||
|
$source = trim( (string) \S::get( 'source' ) );
|
||||||
|
if ( $source !== '' && $source !== 'all' )
|
||||||
|
{
|
||||||
|
$filters['source'] = $source;
|
||||||
|
}
|
||||||
|
|
||||||
|
$date_from = trim( (string) \S::get( 'date_from' ) );
|
||||||
|
if ( $date_from !== '' )
|
||||||
|
{
|
||||||
|
$filters['date_from'] = $date_from;
|
||||||
|
}
|
||||||
|
|
||||||
|
$date_to = trim( (string) \S::get( 'date_to' ) );
|
||||||
|
if ( $date_to !== '' )
|
||||||
|
{
|
||||||
|
$filters['date_to'] = $date_to;
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = \factory\Logs::get_data( $start, $length, $filters );
|
||||||
|
$total = \factory\Logs::get_records_total( $filters );
|
||||||
|
|
||||||
|
$level_badges = [
|
||||||
|
'info' => '<span class="badge badge-success">info</span>',
|
||||||
|
'error' => '<span class="badge badge-danger">error</span>',
|
||||||
|
'warning' => '<span class="badge badge-warning">warning</span>'
|
||||||
|
];
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
foreach ( $rows as $row )
|
||||||
|
{
|
||||||
|
$message = (string) ( $row['message'] ?? '' );
|
||||||
|
if ( mb_strlen( $message ) > 120 )
|
||||||
|
{
|
||||||
|
$message = mb_substr( $message, 0, 120 ) . '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
$client_label = '';
|
||||||
|
if ( !empty( $row['client_name'] ) )
|
||||||
|
{
|
||||||
|
$client_label = htmlspecialchars( (string) $row['client_name'], ENT_QUOTES, 'UTF-8' );
|
||||||
|
}
|
||||||
|
else if ( !empty( $row['client_id'] ) )
|
||||||
|
{
|
||||||
|
$client_label = 'ID: ' . (int) $row['client_id'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$client_label = '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
$data[] = [
|
||||||
|
(string) ( $row['date_add'] ?? '' ),
|
||||||
|
$level_badges[ $row['level'] ] ?? htmlspecialchars( (string) $row['level'], ENT_QUOTES, 'UTF-8' ),
|
||||||
|
htmlspecialchars( (string) ( $row['source'] ?? '' ), ENT_QUOTES, 'UTF-8' ),
|
||||||
|
$client_label,
|
||||||
|
htmlspecialchars( $message, ENT_QUOTES, 'UTF-8' ),
|
||||||
|
'<button type="button" class="btn btn-sm btn-outline-secondary log-detail-btn" data-id="' . (int) $row['id'] . '"><i class="fa-solid fa-eye"></i></button>'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode( [
|
||||||
|
'draw' => $draw,
|
||||||
|
'recordsTotal' => $total,
|
||||||
|
'recordsFiltered' => $total,
|
||||||
|
'data' => $data
|
||||||
|
] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_detail()
|
||||||
|
{
|
||||||
|
$id = (int) \S::get( 'id' );
|
||||||
|
|
||||||
|
if ( $id <= 0 )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'success' => false, 'message' => 'Nieprawidlowe ID.' ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$log = \factory\Logs::get_one( $id );
|
||||||
|
|
||||||
|
if ( !$log )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'success' => false, 'message' => 'Wpis nie znaleziony.' ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = null;
|
||||||
|
if ( !empty( $log['context_json'] ) )
|
||||||
|
{
|
||||||
|
$decoded = json_decode( $log['context_json'], true );
|
||||||
|
if ( $decoded !== null )
|
||||||
|
{
|
||||||
|
$context = json_encode( $decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_INVALID_UTF8_SUBSTITUTE );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$context = $log['context_json'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = json_encode( [
|
||||||
|
'success' => true,
|
||||||
|
'log' => [
|
||||||
|
'id' => (int) $log['id'],
|
||||||
|
'level' => $log['level'],
|
||||||
|
'source' => $log['source'],
|
||||||
|
'client_name' => $log['client_name'] ?? '',
|
||||||
|
'client_id' => $log['client_id'],
|
||||||
|
'message' => $log['message'],
|
||||||
|
'context' => $context,
|
||||||
|
'date_add' => $log['date_add']
|
||||||
|
]
|
||||||
|
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR );
|
||||||
|
|
||||||
|
if ( $response === false )
|
||||||
|
{
|
||||||
|
echo json_encode( [ 'success' => false, 'message' => 'Blad kodowania JSON: ' . json_last_error_msg() ] );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $response;
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -330,7 +330,6 @@ class Users
|
|||||||
[ 'name' => 'Cron uniwersalny (Google Ads)', 'path' => '/cron/cron_universal', 'action' => 'cron_universal', 'plan' => 'Co 1 min: kampanie (wczoraj) + frazy/produkty (7 dni wstecz) + Merchant URL' ],
|
[ 'name' => 'Cron uniwersalny (Google Ads)', 'path' => '/cron/cron_universal', 'action' => 'cron_universal', 'plan' => 'Co 1 min: kampanie (wczoraj) + frazy/produkty (7 dni wstecz) + Merchant URL' ],
|
||||||
[ 'name' => 'Cron alertow kampanii (Merchant)', 'path' => '/cron/cron_campaigns_product_alerts_merchant', 'action' => 'cron_campaigns_product_alerts_merchant', 'plan' => 'Co 15 min: alerty produktowe z Google Merchant' ],
|
[ 'name' => 'Cron alertow kampanii (Merchant)', 'path' => '/cron/cron_campaigns_product_alerts_merchant', 'action' => 'cron_campaigns_product_alerts_merchant', 'plan' => 'Co 15 min: alerty produktowe z Google Merchant' ],
|
||||||
[ 'name' => 'Cron URL produktów (Merchant)', 'path' => '/cron/cron_products_urls', 'action' => 'cron_products_urls', 'plan' => '' ],
|
[ 'name' => 'Cron URL produktów (Merchant)', 'path' => '/cron/cron_products_urls', 'action' => 'cron_products_urls', 'plan' => '' ],
|
||||||
[ 'name' => 'Cron archiwum kampanii', 'path' => '/cron/cron_campaigns_archive', 'action' => 'cron_campaigns_archive', 'plan' => '' ],
|
|
||||||
[ 'name' => 'Cron Facebook Ads', 'path' => '/cron/cron_facebook_ads', 'action' => 'cron_facebook_ads', 'plan' => 'Co 5 min: 30 dni wstecz od wczoraj, blokada ponownego pobrania w tym samym dniu' ],
|
[ 'name' => 'Cron Facebook Ads', 'path' => '/cron/cron_facebook_ads', 'action' => 'cron_facebook_ads', 'plan' => 'Co 5 min: 30 dni wstecz od wczoraj, blokada ponownego pobrania w tym samym dniu' ],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
156
autoload/factory/class.Logs.php
Normal file
156
autoload/factory/class.Logs.php
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
namespace factory;
|
||||||
|
|
||||||
|
class Logs
|
||||||
|
{
|
||||||
|
static public function add( $level, $source, $message, $context = null, $client_id = null )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$level = trim( (string) $level );
|
||||||
|
if ( !in_array( $level, [ 'info', 'error', 'warning' ], true ) )
|
||||||
|
{
|
||||||
|
$level = 'info';
|
||||||
|
}
|
||||||
|
|
||||||
|
$context_json = null;
|
||||||
|
if ( $context !== null )
|
||||||
|
{
|
||||||
|
$context_json = json_encode( $context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PARTIAL_OUTPUT_ON_ERROR );
|
||||||
|
if ( $context_json === false )
|
||||||
|
{
|
||||||
|
$context_json = json_encode( [ '_encoding_error' => json_last_error_msg(), '_type' => gettype( $context ) ] );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mdb -> insert( 'logs', [
|
||||||
|
'level' => $level,
|
||||||
|
'source' => trim( (string) $source ),
|
||||||
|
'client_id' => $client_id !== null ? (int) $client_id : null,
|
||||||
|
'message' => (string) $message,
|
||||||
|
'context_json' => $context_json
|
||||||
|
] );
|
||||||
|
|
||||||
|
return (int) $mdb -> id();
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_data( $start, $length, $filters = [] )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$start = max( 0, (int) $start );
|
||||||
|
$length = max( 1, min( 100, (int) $length ) );
|
||||||
|
|
||||||
|
$where_parts = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ( !empty( $filters['level'] ) )
|
||||||
|
{
|
||||||
|
$where_parts[] = 'l.level = :level';
|
||||||
|
$params[':level'] = (string) $filters['level'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !empty( $filters['source'] ) )
|
||||||
|
{
|
||||||
|
$where_parts[] = 'l.source = :source';
|
||||||
|
$params[':source'] = (string) $filters['source'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !empty( $filters['date_from'] ) )
|
||||||
|
{
|
||||||
|
$where_parts[] = 'l.date_add >= :date_from';
|
||||||
|
$params[':date_from'] = (string) $filters['date_from'] . ' 00:00:00';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !empty( $filters['date_to'] ) )
|
||||||
|
{
|
||||||
|
$where_parts[] = 'l.date_add <= :date_to';
|
||||||
|
$params[':date_to'] = (string) $filters['date_to'] . ' 23:59:59';
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_sql = '';
|
||||||
|
if ( !empty( $where_parts ) )
|
||||||
|
{
|
||||||
|
$where_sql = 'WHERE ' . implode( ' AND ', $where_parts );
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT l.*, c.name AS client_name
|
||||||
|
FROM logs AS l
|
||||||
|
LEFT JOIN clients AS c ON c.id = l.client_id
|
||||||
|
{$where_sql}
|
||||||
|
ORDER BY l.date_add DESC
|
||||||
|
LIMIT {$start}, {$length}";
|
||||||
|
|
||||||
|
return $mdb -> query( $sql, $params ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_records_total( $filters = [] )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$where_parts = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
if ( !empty( $filters['level'] ) )
|
||||||
|
{
|
||||||
|
$where_parts[] = 'level = :level';
|
||||||
|
$params[':level'] = (string) $filters['level'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !empty( $filters['source'] ) )
|
||||||
|
{
|
||||||
|
$where_parts[] = 'source = :source';
|
||||||
|
$params[':source'] = (string) $filters['source'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !empty( $filters['date_from'] ) )
|
||||||
|
{
|
||||||
|
$where_parts[] = 'date_add >= :date_from';
|
||||||
|
$params[':date_from'] = (string) $filters['date_from'] . ' 00:00:00';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !empty( $filters['date_to'] ) )
|
||||||
|
{
|
||||||
|
$where_parts[] = 'date_add <= :date_to';
|
||||||
|
$params[':date_to'] = (string) $filters['date_to'] . ' 23:59:59';
|
||||||
|
}
|
||||||
|
|
||||||
|
$where_sql = '';
|
||||||
|
if ( !empty( $where_parts ) )
|
||||||
|
{
|
||||||
|
$where_sql = 'WHERE ' . implode( ' AND ', $where_parts );
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $mdb -> query( "SELECT COUNT(*) AS cnt FROM logs {$where_sql}", $params ) -> fetch( \PDO::FETCH_ASSOC );
|
||||||
|
return (int) ( $row['cnt'] ?? 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_one( $id )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
return $mdb -> query(
|
||||||
|
"SELECT l.*, c.name AS client_name
|
||||||
|
FROM logs AS l
|
||||||
|
LEFT JOIN clients AS c ON c.id = l.client_id
|
||||||
|
WHERE l.id = :id",
|
||||||
|
[ ':id' => (int) $id ]
|
||||||
|
) -> fetch( \PDO::FETCH_ASSOC );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function cleanup_old( $days = 30 )
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$days = max( 1, (int) $days );
|
||||||
|
$mdb -> query( "DELETE FROM logs WHERE date_add < DATE_SUB( NOW(), INTERVAL {$days} DAY )" );
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function get_sources()
|
||||||
|
{
|
||||||
|
global $mdb;
|
||||||
|
|
||||||
|
$rows = $mdb -> query( "SELECT DISTINCT source FROM logs WHERE source != '' ORDER BY source ASC" ) -> fetchAll( \PDO::FETCH_COLUMN );
|
||||||
|
return is_array( $rows ) ? $rows : [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1952,6 +1952,7 @@ class GoogleAdsApi
|
|||||||
. "segments.product_link, "
|
. "segments.product_link, "
|
||||||
. "campaign.id, "
|
. "campaign.id, "
|
||||||
. "campaign.name, "
|
. "campaign.name, "
|
||||||
|
. "campaign.status, "
|
||||||
. "campaign.advertising_channel_type, "
|
. "campaign.advertising_channel_type, "
|
||||||
. "ad_group.id, "
|
. "ad_group.id, "
|
||||||
. "ad_group.name, "
|
. "ad_group.name, "
|
||||||
@@ -1962,6 +1963,7 @@ class GoogleAdsApi
|
|||||||
. "metrics.conversions_value "
|
. "metrics.conversions_value "
|
||||||
. "FROM shopping_performance_view "
|
. "FROM shopping_performance_view "
|
||||||
. "WHERE segments.date = '" . $date . "' "
|
. "WHERE segments.date = '" . $date . "' "
|
||||||
|
. "AND campaign.status = 'ENABLED' "
|
||||||
. "AND campaign.advertising_channel_type = 'SHOPPING'";
|
. "AND campaign.advertising_channel_type = 'SHOPPING'";
|
||||||
|
|
||||||
$gaql_with_ad_group = "SELECT "
|
$gaql_with_ad_group = "SELECT "
|
||||||
@@ -1970,6 +1972,7 @@ class GoogleAdsApi
|
|||||||
. "segments.product_title, "
|
. "segments.product_title, "
|
||||||
. "campaign.id, "
|
. "campaign.id, "
|
||||||
. "campaign.name, "
|
. "campaign.name, "
|
||||||
|
. "campaign.status, "
|
||||||
. "campaign.advertising_channel_type, "
|
. "campaign.advertising_channel_type, "
|
||||||
. "ad_group.id, "
|
. "ad_group.id, "
|
||||||
. "ad_group.name, "
|
. "ad_group.name, "
|
||||||
@@ -1980,6 +1983,7 @@ class GoogleAdsApi
|
|||||||
. "metrics.conversions_value "
|
. "metrics.conversions_value "
|
||||||
. "FROM shopping_performance_view "
|
. "FROM shopping_performance_view "
|
||||||
. "WHERE segments.date = '" . $date . "' "
|
. "WHERE segments.date = '" . $date . "' "
|
||||||
|
. "AND campaign.status = 'ENABLED' "
|
||||||
. "AND campaign.advertising_channel_type = 'SHOPPING'";
|
. "AND campaign.advertising_channel_type = 'SHOPPING'";
|
||||||
|
|
||||||
$gaql_pmax_asset_group_with_url = "SELECT "
|
$gaql_pmax_asset_group_with_url = "SELECT "
|
||||||
@@ -1989,6 +1993,7 @@ class GoogleAdsApi
|
|||||||
. "segments.product_link, "
|
. "segments.product_link, "
|
||||||
. "campaign.id, "
|
. "campaign.id, "
|
||||||
. "campaign.name, "
|
. "campaign.name, "
|
||||||
|
. "campaign.status, "
|
||||||
. "campaign.advertising_channel_type, "
|
. "campaign.advertising_channel_type, "
|
||||||
. "asset_group.id, "
|
. "asset_group.id, "
|
||||||
. "asset_group.name, "
|
. "asset_group.name, "
|
||||||
@@ -1999,6 +2004,7 @@ class GoogleAdsApi
|
|||||||
. "metrics.conversions_value "
|
. "metrics.conversions_value "
|
||||||
. "FROM asset_group_product_group_view "
|
. "FROM asset_group_product_group_view "
|
||||||
. "WHERE segments.date = '" . $date . "' "
|
. "WHERE segments.date = '" . $date . "' "
|
||||||
|
. "AND campaign.status = 'ENABLED' "
|
||||||
. "AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'";
|
. "AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'";
|
||||||
|
|
||||||
$gaql_pmax_asset_group = "SELECT "
|
$gaql_pmax_asset_group = "SELECT "
|
||||||
@@ -2007,6 +2013,7 @@ class GoogleAdsApi
|
|||||||
. "segments.product_title, "
|
. "segments.product_title, "
|
||||||
. "campaign.id, "
|
. "campaign.id, "
|
||||||
. "campaign.name, "
|
. "campaign.name, "
|
||||||
|
. "campaign.status, "
|
||||||
. "campaign.advertising_channel_type, "
|
. "campaign.advertising_channel_type, "
|
||||||
. "asset_group.id, "
|
. "asset_group.id, "
|
||||||
. "asset_group.name, "
|
. "asset_group.name, "
|
||||||
@@ -2017,6 +2024,7 @@ class GoogleAdsApi
|
|||||||
. "metrics.conversions_value "
|
. "metrics.conversions_value "
|
||||||
. "FROM asset_group_product_group_view "
|
. "FROM asset_group_product_group_view "
|
||||||
. "WHERE segments.date = '" . $date . "' "
|
. "WHERE segments.date = '" . $date . "' "
|
||||||
|
. "AND campaign.status = 'ENABLED' "
|
||||||
. "AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'";
|
. "AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'";
|
||||||
|
|
||||||
$gaql_pmax_campaign_level_fallback_with_url = "SELECT "
|
$gaql_pmax_campaign_level_fallback_with_url = "SELECT "
|
||||||
@@ -2026,6 +2034,7 @@ class GoogleAdsApi
|
|||||||
. "segments.product_link, "
|
. "segments.product_link, "
|
||||||
. "campaign.id, "
|
. "campaign.id, "
|
||||||
. "campaign.name, "
|
. "campaign.name, "
|
||||||
|
. "campaign.status, "
|
||||||
. "campaign.advertising_channel_type, "
|
. "campaign.advertising_channel_type, "
|
||||||
. "metrics.impressions, "
|
. "metrics.impressions, "
|
||||||
. "metrics.clicks, "
|
. "metrics.clicks, "
|
||||||
@@ -2034,6 +2043,7 @@ class GoogleAdsApi
|
|||||||
. "metrics.conversions_value "
|
. "metrics.conversions_value "
|
||||||
. "FROM shopping_performance_view "
|
. "FROM shopping_performance_view "
|
||||||
. "WHERE segments.date = '" . $date . "' "
|
. "WHERE segments.date = '" . $date . "' "
|
||||||
|
. "AND campaign.status = 'ENABLED' "
|
||||||
. "AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'";
|
. "AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'";
|
||||||
|
|
||||||
$gaql_pmax_campaign_level_fallback = "SELECT "
|
$gaql_pmax_campaign_level_fallback = "SELECT "
|
||||||
@@ -2042,6 +2052,7 @@ class GoogleAdsApi
|
|||||||
. "segments.product_title, "
|
. "segments.product_title, "
|
||||||
. "campaign.id, "
|
. "campaign.id, "
|
||||||
. "campaign.name, "
|
. "campaign.name, "
|
||||||
|
. "campaign.status, "
|
||||||
. "campaign.advertising_channel_type, "
|
. "campaign.advertising_channel_type, "
|
||||||
. "metrics.impressions, "
|
. "metrics.impressions, "
|
||||||
. "metrics.clicks, "
|
. "metrics.clicks, "
|
||||||
@@ -2050,6 +2061,7 @@ class GoogleAdsApi
|
|||||||
. "metrics.conversions_value "
|
. "metrics.conversions_value "
|
||||||
. "FROM shopping_performance_view "
|
. "FROM shopping_performance_view "
|
||||||
. "WHERE segments.date = '" . $date . "' "
|
. "WHERE segments.date = '" . $date . "' "
|
||||||
|
. "AND campaign.status = 'ENABLED' "
|
||||||
. "AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'";
|
. "AND campaign.advertising_channel_type = 'PERFORMANCE_MAX'";
|
||||||
|
|
||||||
$search_with_optional_url = function( $query_with_url, $query_without_url ) use ( $customer_id )
|
$search_with_optional_url = function( $query_with_url, $query_without_url ) use ( $customer_id )
|
||||||
|
|||||||
12
autoload/view/class.Logs.php
Normal file
12
autoload/view/class.Logs.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
namespace view;
|
||||||
|
|
||||||
|
class Logs
|
||||||
|
{
|
||||||
|
static public function main_view( $sources = [] )
|
||||||
|
{
|
||||||
|
return \Tpl::view( 'logs/main_view', [
|
||||||
|
'sources' => $sources,
|
||||||
|
] );
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -49,6 +49,9 @@ $route_aliases = [
|
|||||||
'settings/save_claude' => ['users', 'settings_save_claude'],
|
'settings/save_claude' => ['users', 'settings_save_claude'],
|
||||||
'products/ai_suggest' => ['products', 'ai_suggest'],
|
'products/ai_suggest' => ['products', 'ai_suggest'],
|
||||||
'clients/save' => ['clients', 'save'],
|
'clients/save' => ['clients', 'save'],
|
||||||
|
'logs' => ['logs', 'main_view'],
|
||||||
|
'logs/get_data_table' => ['logs', 'get_logs_data_table'],
|
||||||
|
'logs/get_detail' => ['logs', 'get_detail'],
|
||||||
];
|
];
|
||||||
|
|
||||||
$path = implode('/', $segments);
|
$path = implode('/', $segments);
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3532,3 +3532,213 @@ table#products {
|
|||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
background: #2563EB;
|
background: #2563EB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===========================
|
||||||
|
// LOGS PAGE
|
||||||
|
// ===========================
|
||||||
|
.logs-page {
|
||||||
|
.logs-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $cTextDark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-filters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.filter-group {
|
||||||
|
flex: 1 1 160px;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 220px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: #8899A6;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid $cBorder;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $cTextDark;
|
||||||
|
background: $cWhite;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: $cPrimary;
|
||||||
|
box-shadow: 0 0 0 3px rgba($cPrimary, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
select.form-control {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238899A6' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 12px center;
|
||||||
|
padding-right: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.filter-group-buttons {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logs-table-wrap {
|
||||||
|
background: $cWhite;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.table {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
background: #F0F4FA;
|
||||||
|
border-bottom: 2px solid $cBorder;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
color: #8899A6;
|
||||||
|
padding: 12px 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody td {
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: $cTextDark;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-bottom: 1px solid #EEF2F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:hover td {
|
||||||
|
background: #F8FAFD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dt-layout-row {
|
||||||
|
padding: 14px 20px;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-top: 1px solid #F1F5F9;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dt-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #8899A6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dt-paging {
|
||||||
|
.pagination {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.page-item {
|
||||||
|
.page-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 36px;
|
||||||
|
width: fit-content;
|
||||||
|
height: 36px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid $cBorder;
|
||||||
|
background: $cWhite;
|
||||||
|
color: $cText;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #EEF2FF;
|
||||||
|
color: $cPrimary;
|
||||||
|
border-color: $cPrimary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .page-link {
|
||||||
|
background: $cPrimary;
|
||||||
|
color: $cWhite;
|
||||||
|
border-color: $cPrimary;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled .page-link {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dt-processing {
|
||||||
|
background: rgba($cWhite, 0.9);
|
||||||
|
color: $cText;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-success {
|
||||||
|
background: #D1FAE5;
|
||||||
|
color: #065F46;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-danger {
|
||||||
|
background: #FEE2E2;
|
||||||
|
color: #991B1B;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-warning {
|
||||||
|
background: #FEF3C7;
|
||||||
|
color: #92400E;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -383,4 +383,15 @@
|
|||||||
return new AdsProDialog( options );
|
return new AdsProDialog( options );
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$.dialog = function( options )
|
||||||
|
{
|
||||||
|
options = options || {};
|
||||||
|
options.closeIcon = true;
|
||||||
|
if ( !options.buttons )
|
||||||
|
{
|
||||||
|
options.buttons = {};
|
||||||
|
}
|
||||||
|
return new AdsProDialog( options );
|
||||||
|
};
|
||||||
|
|
||||||
})( jQuery );
|
})( jQuery );
|
||||||
|
|||||||
14
migrations/023_logs.sql
Normal file
14
migrations/023_logs.sql
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS `logs` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`level` varchar(20) NOT NULL DEFAULT 'info',
|
||||||
|
`source` varchar(120) NOT NULL DEFAULT '',
|
||||||
|
`client_id` int(11) DEFAULT NULL,
|
||||||
|
`message` text NOT NULL,
|
||||||
|
`context_json` longtext DEFAULT NULL,
|
||||||
|
`date_add` datetime NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `idx_logs_level` (`level`),
|
||||||
|
KEY `idx_logs_source` (`source`),
|
||||||
|
KEY `idx_logs_client` (`client_id`),
|
||||||
|
KEY `idx_logs_date` (`date_add`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<table class="table" id="products">
|
<table class="table" id="products">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 30px; text-align: center;"><input type="checkbox" id="select_all_history"></th>
|
<th style="width: 30px; text-align: center;" data-orderable="false" data-dt-order="disable"><input type="checkbox" id="select_all_history"></th>
|
||||||
<th>Data</th>
|
<th>Data</th>
|
||||||
<th>ROAS (30 dni)</th>
|
<th>ROAS (30 dni)</th>
|
||||||
<th>ROAS (all time)</th>
|
<th>ROAS (all time)</th>
|
||||||
@@ -61,11 +61,80 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
.campaigns-page input.ads-pretty-checkbox {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 1.5px solid #B8C5D6;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #FFFFFF;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaigns-page input.ads-pretty-checkbox:hover {
|
||||||
|
border-color: #6690F4;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 144, 244, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaigns-page input.ads-pretty-checkbox:checked {
|
||||||
|
background: #6690F4;
|
||||||
|
border-color: #6690F4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaigns-page input.ads-pretty-checkbox:checked::after {
|
||||||
|
content: '';
|
||||||
|
width: 8px;
|
||||||
|
height: 4px;
|
||||||
|
border: 2px solid #FFFFFF;
|
||||||
|
border-top: 0;
|
||||||
|
border-right: 0;
|
||||||
|
transform: rotate(-45deg) translate(1px, -1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaigns-page input.ads-pretty-checkbox:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 3px rgba(102, 144, 244, 0.26);
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaigns-page #products thead th:first-child {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var STORAGE_CLIENT_KEY = 'campaigns.last_client_id';
|
var STORAGE_CLIENT_KEY = 'campaigns.last_client_id';
|
||||||
var STORAGE_CAMPAIGN_KEY = 'campaigns.last_campaign_id';
|
var STORAGE_CAMPAIGN_KEY = 'campaigns.last_campaign_id';
|
||||||
var restore_campaign_after_client_load = '';
|
var restore_campaign_after_client_load = '';
|
||||||
|
|
||||||
|
if ( typeof $.fn.adsPrettyCheckbox === 'undefined' )
|
||||||
|
{
|
||||||
|
$.fn.adsPrettyCheckbox = function()
|
||||||
|
{
|
||||||
|
return this.each( function()
|
||||||
|
{
|
||||||
|
var input = $( this );
|
||||||
|
if ( !input.is( 'input[type="checkbox"]' ) )
|
||||||
|
return;
|
||||||
|
|
||||||
|
input.addClass( 'ads-pretty-checkbox' );
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPrettyCheckboxes( context )
|
||||||
|
{
|
||||||
|
var root = context ? $( context ) : $( document );
|
||||||
|
root.find( 'input[type="checkbox"]' ).adsPrettyCheckbox();
|
||||||
|
}
|
||||||
|
|
||||||
function storage_set( key, value )
|
function storage_set( key, value )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -105,6 +174,7 @@ function rebuildCampaignDropdown()
|
|||||||
menu.append( item );
|
menu.append( item );
|
||||||
});
|
});
|
||||||
|
|
||||||
|
initPrettyCheckboxes( menu );
|
||||||
updateCheckboxState();
|
updateCheckboxState();
|
||||||
updateDropdownDisplay();
|
updateDropdownDisplay();
|
||||||
}
|
}
|
||||||
@@ -235,6 +305,8 @@ function reloadChart()
|
|||||||
|
|
||||||
$( function()
|
$( function()
|
||||||
{
|
{
|
||||||
|
initPrettyCheckboxes( '.campaigns-page' );
|
||||||
|
|
||||||
$( 'body' ).on( 'click', '#campaign_dropdown_trigger', function( e )
|
$( 'body' ).on( 'click', '#campaign_dropdown_trigger', function( e )
|
||||||
{
|
{
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -494,14 +566,14 @@ $( function()
|
|||||||
pageLength: 15,
|
pageLength: 15,
|
||||||
columns: [
|
columns: [
|
||||||
{ width: '30px', orderable: false, className: 'dt-center', searchable: false },
|
{ width: '30px', orderable: false, className: 'dt-center', searchable: false },
|
||||||
{ width: '130px', name: 'date', orderable: false, className: "nowrap" },
|
{ width: '130px', name: 'date', orderable: true, className: "nowrap" },
|
||||||
{ width: '120px', name: 'roas30', orderable: false, className: "dt-type-numeric" },
|
{ width: '120px', name: 'roas30', orderable: true, className: "dt-type-numeric" },
|
||||||
{ width: '120px', name: 'roas_all_time', orderable: false, className: "dt-type-numeric" },
|
{ width: '120px', name: 'roas_all_time', orderable: true, className: "dt-type-numeric" },
|
||||||
{ width: '180px', name: 'conversion_value', orderable: false, className: "dt-type-numeric" },
|
{ width: '180px', name: 'conversion_value', orderable: true, className: "dt-type-numeric" },
|
||||||
{ width: '140px', name: 'spend30', orderable: false, className: "dt-type-numeric" },
|
{ width: '140px', name: 'spend30', orderable: true, className: "dt-type-numeric" },
|
||||||
{ width: 'auto', name: 'comment', orderable: false },
|
{ width: 'auto', name: 'comment', orderable: true },
|
||||||
{ width: 'auto', name: 'bidding_strategy', orderable: false },
|
{ width: 'auto', name: 'bidding_strategy', orderable: true },
|
||||||
{ width: '100px', name: 'budget', orderable: false, className: "dt-type-numeric" },
|
{ width: '100px', name: 'budget', orderable: true, className: "dt-type-numeric" },
|
||||||
{ width: '60px', name: 'actions', orderable: false, className: "dt-center" }
|
{ width: '60px', name: 'actions', orderable: false, className: "dt-center" }
|
||||||
],
|
],
|
||||||
language: {
|
language: {
|
||||||
@@ -554,6 +626,7 @@ $( function()
|
|||||||
|
|
||||||
$( 'body' ).on( 'draw.dt', '#products', function()
|
$( 'body' ).on( 'draw.dt', '#products', function()
|
||||||
{
|
{
|
||||||
|
initPrettyCheckboxes( '#products' );
|
||||||
$( '#select_all_history' ).prop( 'checked', false );
|
$( '#select_all_history' ).prop( 'checked', false );
|
||||||
updateHistoryBulkActions();
|
updateHistoryBulkActions();
|
||||||
});
|
});
|
||||||
|
|||||||
191
templates/logs/main_view.php
Normal file
191
templates/logs/main_view.php
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
<div class="logs-page">
|
||||||
|
<div class="logs-header">
|
||||||
|
<h2>Logi systemowe</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="logs-filters">
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>Poziom</label>
|
||||||
|
<select id="filter_level" class="form-control">
|
||||||
|
<option value="all">Wszystkie</option>
|
||||||
|
<option value="error">Error</option>
|
||||||
|
<option value="warning">Warning</option>
|
||||||
|
<option value="info">Info</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>Zrodlo</label>
|
||||||
|
<select id="filter_source" class="form-control">
|
||||||
|
<option value="all">Wszystkie</option>
|
||||||
|
<?php foreach ( $this -> sources as $source ): ?>
|
||||||
|
<option value="<?= htmlspecialchars( $source, ENT_QUOTES, 'UTF-8' ); ?>"><?= htmlspecialchars( $source, ENT_QUOTES, 'UTF-8' ); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>Data od</label>
|
||||||
|
<input type="date" id="filter_date_from" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="filter-group">
|
||||||
|
<label>Data do</label>
|
||||||
|
<input type="date" id="filter_date_to" class="form-control">
|
||||||
|
</div>
|
||||||
|
<div class="filter-group filter-group-buttons">
|
||||||
|
<button type="button" id="filter_apply" class="btn btn-primary btn-sm">
|
||||||
|
<i class="fa-solid fa-filter"></i> Filtruj
|
||||||
|
</button>
|
||||||
|
<button type="button" id="filter_reset" class="btn btn-secondary btn-sm">
|
||||||
|
<i class="fa-solid fa-times"></i> Resetuj
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="logs-table-wrap">
|
||||||
|
<table class="table" id="logs-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Data</th>
|
||||||
|
<th>Poziom</th>
|
||||||
|
<th>Zrodlo</th>
|
||||||
|
<th>Klient</th>
|
||||||
|
<th>Wiadomosc</th>
|
||||||
|
<th style="width: 50px; text-align: center;">Akcje</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
var logsTable = null;
|
||||||
|
|
||||||
|
function getFilterParams()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
level: $( '#filter_level' ).val(),
|
||||||
|
source: $( '#filter_source' ).val(),
|
||||||
|
date_from: $( '#filter_date_from' ).val(),
|
||||||
|
date_to: $( '#filter_date_to' ).val()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function initLogsTable()
|
||||||
|
{
|
||||||
|
if ( logsTable )
|
||||||
|
{
|
||||||
|
logsTable.destroy();
|
||||||
|
$( '#logs-table tbody' ).empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
logsTable = new DataTable( '#logs-table', {
|
||||||
|
ajax: {
|
||||||
|
url: '/logs/get_data_table/',
|
||||||
|
data: function( d )
|
||||||
|
{
|
||||||
|
var f = getFilterParams();
|
||||||
|
d.level = f.level;
|
||||||
|
d.source = f.source;
|
||||||
|
d.date_from = f.date_from;
|
||||||
|
d.date_to = f.date_to;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
processing: true,
|
||||||
|
serverSide: true,
|
||||||
|
searching: false,
|
||||||
|
lengthChange: false,
|
||||||
|
pageLength: 25,
|
||||||
|
order: [[ 0, 'desc' ]],
|
||||||
|
columns: [
|
||||||
|
{ width: '150px', orderable: false, className: 'nowrap' },
|
||||||
|
{ width: '70px', orderable: false, className: 'dt-center' },
|
||||||
|
{ width: '140px', orderable: false },
|
||||||
|
{ width: '140px', orderable: false },
|
||||||
|
{ orderable: false },
|
||||||
|
{ width: '50px', orderable: false, className: 'dt-center' }
|
||||||
|
],
|
||||||
|
language: {
|
||||||
|
processing: 'Ladowanie...',
|
||||||
|
emptyTable: 'Brak logow do wyswietlenia',
|
||||||
|
info: 'Wpisy _START_ - _END_ z _TOTAL_',
|
||||||
|
infoEmpty: '',
|
||||||
|
paginate: { previous: '«', next: '»' }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$( function()
|
||||||
|
{
|
||||||
|
initLogsTable();
|
||||||
|
|
||||||
|
$( '#filter_apply' ).on( 'click', function()
|
||||||
|
{
|
||||||
|
if ( logsTable )
|
||||||
|
{
|
||||||
|
logsTable.ajax.reload( null, true );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( '#filter_reset' ).on( 'click', function()
|
||||||
|
{
|
||||||
|
$( '#filter_level' ).val( 'all' );
|
||||||
|
$( '#filter_source' ).val( 'all' );
|
||||||
|
$( '#filter_date_from' ).val( '' );
|
||||||
|
$( '#filter_date_to' ).val( '' );
|
||||||
|
|
||||||
|
if ( logsTable )
|
||||||
|
{
|
||||||
|
logsTable.ajax.reload( null, true );
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$( 'body' ).on( 'click', '.log-detail-btn', function()
|
||||||
|
{
|
||||||
|
var id = $( this ).data( 'id' );
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/logs/get_detail/',
|
||||||
|
type: 'GET',
|
||||||
|
dataType: 'json',
|
||||||
|
data: { id: id },
|
||||||
|
success: function( data )
|
||||||
|
{
|
||||||
|
if ( !data || !data.success )
|
||||||
|
{
|
||||||
|
$.alert({ title: 'Blad', content: ( data && data.message ) || 'Nie udalo sie pobrac szczegolow.', type: 'red' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var log = data.log;
|
||||||
|
var levelClass = log.level === 'error' ? 'danger' : ( log.level === 'warning' ? 'warning' : 'success' );
|
||||||
|
|
||||||
|
var html = '<div style="margin-bottom: 10px;">';
|
||||||
|
html += '<strong>Data:</strong> ' + $( '<span>' ).text( log.date_add ).html() + '<br>';
|
||||||
|
html += '<strong>Poziom:</strong> <span class="badge badge-' + levelClass + '">' + log.level + '</span><br>';
|
||||||
|
html += '<strong>Zrodlo:</strong> ' + $( '<span>' ).text( log.source || '-' ).html() + '<br>';
|
||||||
|
html += '<strong>Klient:</strong> ' + $( '<span>' ).text( log.client_name || ( log.client_id ? 'ID: ' + log.client_id : '-' ) ).html() + '<br>';
|
||||||
|
html += '<strong>Wiadomosc:</strong> ' + $( '<span>' ).text( log.message ).html();
|
||||||
|
html += '</div>';
|
||||||
|
|
||||||
|
if ( log.context )
|
||||||
|
{
|
||||||
|
html += '<div style="margin-top: 10px;">';
|
||||||
|
html += '<strong>Kontekst (JSON):</strong>';
|
||||||
|
html += '<pre style="background: #1e1e2e; color: #cdd6f4; padding: 12px; border-radius: 6px; max-height: 400px; overflow: auto; font-size: 12px; margin-top: 5px;">' + $( '<div>' ).text( log.context ).html() + '</pre>';
|
||||||
|
html += '</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$.dialog({
|
||||||
|
title: 'Szczegoly logu #' + log.id,
|
||||||
|
content: html,
|
||||||
|
columnClass: 'xlarge'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function( xhr )
|
||||||
|
{
|
||||||
|
$.alert({ title: 'Blad', content: 'Blad pobierania szczegolow (HTTP ' + xhr.status + ')', type: 'red' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -123,6 +123,12 @@
|
|||||||
<span>Allegro import</span>
|
<span>Allegro import</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="<?= $module === 'logs' ? 'active' : '' ?>">
|
||||||
|
<a href="/logs">
|
||||||
|
<i class="fa-solid fa-file-lines"></i>
|
||||||
|
<span>Logi</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-divider"></li>
|
<li class="nav-divider"></li>
|
||||||
<li class="<?= $module === 'users' ? 'active' : '' ?>">
|
<li class="<?= $module === 'users' ? 'active' : '' ?>">
|
||||||
<a href="/settings">
|
<a href="/settings">
|
||||||
@@ -165,6 +171,7 @@
|
|||||||
'xml_files' => 'Pliki XML',
|
'xml_files' => 'Pliki XML',
|
||||||
'facebook_ads' => 'Facebook Ads',
|
'facebook_ads' => 'Facebook Ads',
|
||||||
'allegro' => 'Allegro import',
|
'allegro' => 'Allegro import',
|
||||||
|
'logs' => 'Logi',
|
||||||
'users' => 'Ustawienia',
|
'users' => 'Ustawienia',
|
||||||
];
|
];
|
||||||
echo $breadcrumbs[$module] ?? 'adsPRO';
|
echo $breadcrumbs[$module] ?? 'adsPRO';
|
||||||
|
|||||||
Reference in New Issue
Block a user