1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 04:38:53 +03:00
This commit is contained in:
Anton Afanasyeu
2026-06-05 22:11:38 +02:00
parent a656f40922
commit cd2e69ad6b
12 changed files with 368 additions and 62 deletions

View File

@@ -1282,6 +1282,72 @@ button.report-tag--filter:hover {
pointer-events: none;
}
.build-col-controls { width: 4.5rem; }
.build-row-controls { vertical-align: middle; }
.build-transport-actions {
display: inline-flex;
align-items: center;
gap: 6px;
}
.build-transport-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
padding: 0;
border-radius: 50%;
border: 1px solid var(--border);
background: var(--surface);
cursor: pointer;
box-shadow:
0 0 0 1px rgba(61, 139, 253, .12),
0 2px 8px rgba(0, 0, 0, .25),
0 0 14px rgba(61, 139, 253, .1);
transition: box-shadow .18s ease, border-color .18s ease, transform .12s ease;
}
.build-transport-btn:hover:not(:disabled),
.build-transport-btn:focus-visible:not(:disabled) {
border-color: var(--accent);
box-shadow:
0 0 0 1px rgba(61, 139, 253, .45),
0 0 18px rgba(61, 139, 253, .35);
outline: none;
}
.build-transport-btn:active:not(:disabled) { transform: translateY(1px); }
.build-transport-btn:disabled {
opacity: .35;
cursor: not-allowed;
box-shadow: none;
}
.build-transport-play::before {
content: '';
display: block;
width: 0;
height: 0;
margin-left: 3px;
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
border-left: 10px solid var(--accent);
filter: drop-shadow(0 0 4px rgba(61, 139, 253, .45));
}
.build-transport-stop {
border-color: rgba(248, 113, 113, .55);
box-shadow:
0 0 0 1px rgba(248, 113, 113, .25),
0 2px 8px rgba(0, 0, 0, .25),
0 0 12px rgba(248, 113, 113, .18);
}
.build-transport-stop::before {
content: '';
display: block;
width: 9px;
height: 9px;
border-radius: 2px;
background: var(--danger);
box-shadow: 0 0 8px rgba(248, 113, 113, .55);
}
.tag-edit-btn {
flex-shrink: 0;
margin-left: 2px;

View File

@@ -40,6 +40,48 @@
.replace(/"/g, '"');
}
function graphPermalink(scopeKey, suffix, days) {
const url = new URL(window.location.href);
url.searchParams.set('graph', scopeKey + '-' + suffix);
if (days) {
url.searchParams.set('days', String(days));
}
return url.toString();
}
function syncGraphPermalink(scopeKey, suffix) {
const daysSel = el('graphs-days');
const days = daysSel && daysSel.value ? daysSel.value : '14';
const link = graphPermalink(scopeKey, suffix, days);
if (window.history && window.history.replaceState) {
window.history.replaceState(null, '', link);
}
const share = el('graph-detail-share');
if (share) {
share.href = link;
}
}
function clearGraphPermalink() {
const url = new URL(window.location.href);
url.searchParams.delete('graph');
if (window.history && window.history.replaceState) {
window.history.replaceState(null, '', url.toString());
}
}
function openGraphFromPermalink() {
const params = new URLSearchParams(window.location.search);
const token = params.get('graph');
if (!token) return;
const node = document.getElementById('graph-' + token);
if (!node) return;
const card = node.closest('.card.card--lift');
if (card) {
openGraphDetail(card);
}
}
function parseCanvasId(id) {
const m = String(id || '').match(/^graph-(user|slug|platform)-(.+)$/);
if (!m) return null;
@@ -430,6 +472,7 @@
const overlay = el('graph-detail-overlay');
if (overlay) overlay.hidden = true;
detailState = null;
clearGraphPermalink();
const tip = el('graph-detail-tooltip');
if (tip) tip.hidden = true;
const statsEl = el('graph-detail-stats');
@@ -449,6 +492,7 @@
const scope = scopeData(parsed.scopeKey);
if (!scope) return;
const def = parsed.def;
syncGraphPermalink(parsed.scopeKey, parsed.suffix);
const fieldVal = scope[def.field];
const titleEl = el('graph-detail-title');
const canvasEl = el('graph-detail-canvas');
@@ -655,6 +699,21 @@
});
const closeBtn = el('graph-detail-close');
if (closeBtn) closeBtn.addEventListener('click', closeGraphDetail);
const copyBtn = el('graph-detail-copy-link');
if (copyBtn) {
copyBtn.addEventListener('click', function () {
const share = el('graph-detail-share');
const link = (share && share.href && share.href !== '#') ? share.href : window.location.href;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(link).then(function () {
copyBtn.textContent = 'Copied';
setTimeout(function () { copyBtn.textContent = 'Copy link'; }, 1500);
}).catch(function () { window.prompt('Copy graph link:', link); });
} else {
window.prompt('Copy graph link:', link);
}
});
}
}
document.addEventListener('keydown', (ev) => {
if (ev.key === 'Escape') closeGraphDetail();
@@ -680,6 +739,11 @@
});
function load() {
const params = new URLSearchParams(window.location.search);
const daysParam = params.get('days');
if (daysParam && daysSel) {
daysSel.value = daysParam;
}
const days = Number(daysSel && daysSel.value ? daysSel.value : 14);
status.textContent = 'Loading…';
const xhr = new XMLHttpRequest();
@@ -708,6 +772,7 @@
markGraphBricks();
const cols = colsSel && colsSel.value ? colsSel.value : '2';
status.textContent = 'Loaded · viewer=' + viewer + ' · window=' + days + 'd · columns=' + cols;
openGraphFromPermalink();
};
xhr.onerror = function () {
status.textContent = 'Network error';

View File

@@ -246,7 +246,7 @@ final class GraphRepository {
'viewer' => $viewer,
'links' => self::serviceLinks(),
];
$payload['user'] = self::buildScope($days, $activeCompanyId, $scopeUserId);
$payload['user'] = self::buildScope($days, $activeCompanyId, null);
if ($viewer === 'user') {
return $payload;
}

View File

@@ -94,6 +94,23 @@ function json_out(array $data, int $code = 200): void {
exit;
}
/** Public URL prefix for the active console view (graphs vs crashes). */
function console_base_path(?string $view = null): string {
$view = $view ?? (string) ($_GET['view'] ?? '');
if ($view === 'graphs') {
$graphs = trim((string) cfg('graphs_base_path', ''));
if ($graphs !== '') {
return rtrim($graphs, '/');
}
$crashes = rtrim((string) cfg('base_path', ''), '/');
if (str_ends_with($crashes, '/crashes')) {
return substr($crashes, 0, -strlen('/crashes')) . '/graphs';
}
return '/app/androidcast_project/graphs';
}
return rtrim((string) cfg('base_path', ''), '/');
}
/**
* Raw POST body for crash ingest. Supports plain JSON or Content-Encoding: gzip
* (when nginx gunzip is off or the client talks to PHP directly).

View File

@@ -42,7 +42,7 @@
<?php endif; ?>
<?php AnalyticsHead::render('crashes'); ?>
</head>
<body data-base-path="<?= h(Auth::basePath()) ?>"
<body data-base-path="<?= h(console_base_path($view ?? null)) ?>"
data-view="<?= h($view ?? 'home') ?>"
data-can-tag-edit="<?= Auth::canEditTags() ? '1' : '0' ?>"
<?= (($view ?? '') === 'report' && !empty($report['id'])) ? ' data-report-id="' . (int) $report['id'] . '"' : '' ?>
@@ -357,6 +357,8 @@
<p id="graph-detail-stats" class="graph-detail-stats muted" aria-live="polite"></p>
</div>
<button type="button" class="btn graph-detail-close" id="graph-detail-close" aria-label="Close full view">Close</button>
<button type="button" class="btn" id="graph-detail-copy-link">Copy link</button>
<a class="btn" id="graph-detail-share" href="#" hidden aria-hidden="true"></a>
</header>
<div class="graph-detail-canvas-wrap">
<canvas id="graph-detail-canvas" hidden></canvas>