mirror of
git://f0xx.org/android_cast
synced 2026-07-29 05:17:39 +03:00
next force snapshot
This commit is contained in:
@@ -41,6 +41,23 @@ php -S 127.0.0.1:8080 -t public
|
||||
|
||||
Open http://127.0.0.1:8080/ — login **admin** / **admin**.
|
||||
|
||||
## Google Analytics 4 (optional)
|
||||
|
||||
In `config/config.php`:
|
||||
|
||||
```php
|
||||
'analytics' => [
|
||||
'enabled' => true,
|
||||
'measurement_id' => 'G-XXXXXXXXXX',
|
||||
'debug' => false,
|
||||
],
|
||||
```
|
||||
|
||||
Loads `public/assets/js/analytics.js` from `layout.php` and `login.php`. The project hub at
|
||||
`/app/androidcast_project/` uses the same script; configure `examples/app_hub/assets/analytics.config.js`.
|
||||
|
||||
Events include `page_view`, `console_view` (home, tickets, reports, …), and hub `hub_nav_click` / `hub_open_docs`.
|
||||
|
||||
## RBAC (phase 1)
|
||||
|
||||
Multi-tenant foundation (no admin UI yet):
|
||||
|
||||
@@ -37,4 +37,10 @@ return [
|
||||
'default_company_slug' => 'default',
|
||||
'default_company_id' => 1,
|
||||
],
|
||||
// Google Analytics 4 (hub + crash console share public/assets/js/analytics.js)
|
||||
'analytics' => [
|
||||
'enabled' => false,
|
||||
'measurement_id' => '', // e.g. G-XXXXXXXXXX
|
||||
'debug' => false,
|
||||
],
|
||||
];
|
||||
|
||||
145
examples/crash_reporter/backend/public/assets/js/analytics.js
Normal file
145
examples/crash_reporter/backend/public/assets/js/analytics.js
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Google Analytics 4 for Android Cast web platform (hub + crash console).
|
||||
* Set window.ANDROIDCAST_ANALYTICS before this script loads:
|
||||
* { measurementId: 'G-XXXXXXXX', platform: 'hub'|'crashes', debug: false }
|
||||
*/
|
||||
(function (global) {
|
||||
'use strict';
|
||||
|
||||
var cfg = global.ANDROIDCAST_ANALYTICS || {};
|
||||
var measurementId = (cfg.measurementId || '').trim();
|
||||
var platform = cfg.platform || 'web';
|
||||
var debug = !!cfg.debug;
|
||||
|
||||
function log() {
|
||||
if (debug && global.console) {
|
||||
global.console.log.apply(global.console, ['[analytics]'].concat([].slice.call(arguments)));
|
||||
}
|
||||
}
|
||||
|
||||
function enabled() {
|
||||
return measurementId.length > 0 && /^G-[A-Z0-9]+$/i.test(measurementId);
|
||||
}
|
||||
|
||||
function ensureGtag(cb) {
|
||||
if (!enabled()) {
|
||||
return;
|
||||
}
|
||||
global.dataLayer = global.dataLayer || [];
|
||||
if (!global.gtag) {
|
||||
global.gtag = function () {
|
||||
global.dataLayer.push(arguments);
|
||||
};
|
||||
}
|
||||
if (global.__acAnalyticsLoaded) {
|
||||
if (cb) cb();
|
||||
return;
|
||||
}
|
||||
var s = document.createElement('script');
|
||||
s.async = true;
|
||||
s.src = 'https://www.googletagmanager.com/gtag/js?id=' + encodeURIComponent(measurementId);
|
||||
s.onload = function () {
|
||||
global.__acAnalyticsLoaded = true;
|
||||
global.gtag('js', new Date());
|
||||
global.gtag('config', measurementId, {
|
||||
send_page_view: false,
|
||||
anonymize_ip: true
|
||||
});
|
||||
log('loaded', measurementId);
|
||||
if (cb) cb();
|
||||
};
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
|
||||
function pagePath() {
|
||||
var base = (cfg.basePath || '').replace(/\/$/, '');
|
||||
var path = global.location.pathname || '/';
|
||||
if (base && path.indexOf(base) === 0) {
|
||||
path = path.slice(base.length) || '/';
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function pageView(extra) {
|
||||
if (!enabled()) return;
|
||||
ensureGtag(function () {
|
||||
var params = {
|
||||
page_title: document.title || '',
|
||||
page_location: global.location.href,
|
||||
page_path: pagePath(),
|
||||
platform: platform
|
||||
};
|
||||
if (extra) {
|
||||
for (var k in extra) {
|
||||
if (Object.prototype.hasOwnProperty.call(extra, k)) params[k] = extra[k];
|
||||
}
|
||||
}
|
||||
global.gtag('event', 'page_view', params);
|
||||
log('page_view', params);
|
||||
});
|
||||
}
|
||||
|
||||
function trackEvent(name, params) {
|
||||
if (!enabled() || !name) return;
|
||||
ensureGtag(function () {
|
||||
var payload = params || {};
|
||||
payload.platform = platform;
|
||||
global.gtag('event', name, payload);
|
||||
log('event', name, payload);
|
||||
});
|
||||
}
|
||||
|
||||
function bindConsoleView() {
|
||||
var body = document.body;
|
||||
if (!body) return;
|
||||
var view = body.getAttribute('data-view');
|
||||
if (view) {
|
||||
trackEvent('console_view', { view_name: view });
|
||||
}
|
||||
}
|
||||
|
||||
function bindHubInteractions() {
|
||||
document.addEventListener('click', function (ev) {
|
||||
var t = ev.target;
|
||||
if (!(t instanceof Element)) return;
|
||||
var card = t.closest('.hub-card, .hub-card-link');
|
||||
if (card) {
|
||||
var label = card.getAttribute('data-analytics-label')
|
||||
|| (card.textContent || '').trim().slice(0, 64);
|
||||
trackEvent('hub_nav_click', { link_text: label });
|
||||
return;
|
||||
}
|
||||
if (t.closest('[data-open-docs], #hub-compass-layer, .hub-logo-layer--compass')) {
|
||||
trackEvent('hub_open_docs', { source: 'compass' });
|
||||
}
|
||||
if (t.closest('[data-close-docs]')) {
|
||||
trackEvent('hub_close_docs', {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (!enabled()) {
|
||||
log('disabled (no measurement id)');
|
||||
return;
|
||||
}
|
||||
pageView();
|
||||
if (platform === 'crashes') {
|
||||
bindConsoleView();
|
||||
} else if (platform === 'hub') {
|
||||
bindHubInteractions();
|
||||
}
|
||||
}
|
||||
|
||||
global.AndroidCastAnalytics = {
|
||||
enabled: enabled,
|
||||
pageView: pageView,
|
||||
trackEvent: trackEvent
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})(typeof window !== 'undefined' ? window : this);
|
||||
26
examples/crash_reporter/backend/src/AnalyticsHead.php
Normal file
26
examples/crash_reporter/backend/src/AnalyticsHead.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/** Emits GA4 config + shared analytics.js for PHP views. */
|
||||
final class AnalyticsHead {
|
||||
public static function render(string $platform = 'crashes'): void {
|
||||
$enabled = (bool) cfg('analytics.enabled', false);
|
||||
$measurementId = trim((string) cfg('analytics.measurement_id', ''));
|
||||
if (!$enabled || $measurementId === '') {
|
||||
return;
|
||||
}
|
||||
$basePath = Auth::basePath();
|
||||
$config = [
|
||||
'measurementId' => $measurementId,
|
||||
'platform' => $platform,
|
||||
'basePath' => $basePath,
|
||||
'debug' => (bool) cfg('analytics.debug', false),
|
||||
];
|
||||
$json = json_encode($config, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP);
|
||||
if ($json === false) {
|
||||
return;
|
||||
}
|
||||
echo '<script>window.ANDROIDCAST_ANALYTICS=', $json, ';</script>', "\n";
|
||||
echo '<script src="', h($basePath), '/assets/js/analytics.js" defer></script>', "\n";
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ require_once __DIR__ . '/TicketAttachmentRepository.php';
|
||||
require_once __DIR__ . '/TicketCommentRepository.php';
|
||||
require_once __DIR__ . '/UserRepository.php';
|
||||
require_once __DIR__ . '/TicketRepository.php';
|
||||
require_once __DIR__ . '/AnalyticsHead.php';
|
||||
|
||||
function cfg(string $key, $default = null) {
|
||||
global $config;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<?php if (in_array($view ?? '', ['tickets', 'ticket'], true)): ?>
|
||||
<script src="<?= h(Auth::basePath()) ?>/assets/js/tickets.js" defer></script>
|
||||
<?php endif; ?>
|
||||
<?php AnalyticsHead::render('crashes'); ?>
|
||||
</head>
|
||||
<body data-base-path="<?= h(Auth::basePath()) ?>"
|
||||
data-view="<?= h($view ?? 'home') ?>"
|
||||
|
||||
@@ -26,6 +26,7 @@ $bp = Auth::basePath();
|
||||
</script>
|
||||
<link rel="stylesheet" href="<?= h($bp) ?>/assets/css/app.css">
|
||||
<script src="<?= h($bp) ?>/assets/js/i18n.js" defer></script>
|
||||
<?php AnalyticsHead::render('crashes_login'); ?>
|
||||
</head>
|
||||
<body class="login-page" data-base-path="<?= h($bp) ?>">
|
||||
<div class="login-locale">
|
||||
|
||||
Reference in New Issue
Block a user