From 9eca43b9f877f6c11ff228bad09359020e049c3d Mon Sep 17 00:00:00 2001 From: Anton Afanasyeu Date: Thu, 4 Jun 2026 22:30:03 +0200 Subject: [PATCH] sync with BE / AI request --- .../backend/config/config.example.php | 2 +- .../backend/public/assets/i18n/en.json | 8 +- .../backend/public/assets/i18n/ru.json | 4 +- .../crash_reporter/backend/public/index.php | 2 +- .../ingest_roadmap_tickets_20260604.php | 111 ++++++++++++++++++ .../src/TicketAttachmentRepository.php | 4 +- .../backend/src/TicketCommentRepository.php | 5 +- .../crash_reporter/backend/views/layout.php | 1 + .../views/partials/console_quick_links.php | 2 +- .../backend/views/ticket_detail.php | 2 +- 10 files changed, 124 insertions(+), 17 deletions(-) create mode 100755 examples/crash_reporter/backend/scripts/ingest_roadmap_tickets_20260604.php diff --git a/examples/crash_reporter/backend/config/config.example.php b/examples/crash_reporter/backend/config/config.example.php index f51a251..57e49c8 100644 --- a/examples/crash_reporter/backend/config/config.example.php +++ b/examples/crash_reporter/backend/config/config.example.php @@ -11,7 +11,7 @@ * Digest: SHA256 fcd4a89ef4201736923d0e7e1ad88868bba7e39eadb9fcf0ffb9d1ba7efecd12 */ return [ - 'app_name' => 'Android Cast Crashes', + 'app_name' => 'Android Cast Issues', // Production: '/app/androidcast_project/crashes' — Local php -S: use '' (empty string) 'base_path' => '/app/androidcast_project/crashes', 'db' => [ diff --git a/examples/crash_reporter/backend/public/assets/i18n/en.json b/examples/crash_reporter/backend/public/assets/i18n/en.json index d8bf387..b0b3fbe 100644 --- a/examples/crash_reporter/backend/public/assets/i18n/en.json +++ b/examples/crash_reporter/backend/public/assets/i18n/en.json @@ -1,7 +1,7 @@ { - "app_name": "Android Cast Crashes", + "app_name": "Android Cast Issues", "nav.home": "Home", - "nav.reports": "Reports", + "nav.reports": "Issues", "nav.tickets": "Tickets", "nav.logout": "Logout", "nav.toggle": "Navigation", @@ -11,12 +11,12 @@ "lang.label": "Language", "lang.en": "English", "lang.ru": "Russian", - "home.title": "Crash console", + "home.title": "Issues console", "home.intro": "Anonymous Android Cast crash ingest is active. Use Reports to analyze grouped fingerprints.", "home.upload_api": "Upload API:", "home.schema": "Schema:", "home.ticket_upload_api": "Ticket upload API:", - "reports.title": "Crash reports", + "reports.title": "Issues", "reports.by_time": "By time", "reports.grouped": "Grouped", "reports.per_page": "Per page", diff --git a/examples/crash_reporter/backend/public/assets/i18n/ru.json b/examples/crash_reporter/backend/public/assets/i18n/ru.json index bb382d0..78c3469 100644 --- a/examples/crash_reporter/backend/public/assets/i18n/ru.json +++ b/examples/crash_reporter/backend/public/assets/i18n/ru.json @@ -1,7 +1,7 @@ { "app_name": "Сбои Android Cast", "nav.home": "Главная", - "nav.reports": "Отчёты", + "nav.reports": "Задачи (Issues)", "nav.tickets": "Задачи", "tickets.title": "Задачи", "tickets.empty": "Задач пока нет.", @@ -18,7 +18,7 @@ "home.intro": "Приём анонимных отчётов Android Cast активен. Откройте «Отчёты» для анализа сгруппированных отпечатков.", "home.upload_api": "API загрузки:", "home.schema": "Схема:", - "reports.title": "Отчёты о сбоях", + "reports.title": "Issues", "reports.by_time": "По времени", "reports.grouped": "Группы", "reports.per_page": "На странице", diff --git a/examples/crash_reporter/backend/public/index.php b/examples/crash_reporter/backend/public/index.php index 33e66c6..be0a128 100644 --- a/examples/crash_reporter/backend/public/index.php +++ b/examples/crash_reporter/backend/public/index.php @@ -209,7 +209,7 @@ $pageTitle = match ($view) { 'graphs' => 'Graphs', 'remote_access' => 'Remote access', 'rbac' => 'Access control', - 'reports', 'report' => 'Crash reports', + 'reports', 'report' => 'Issues', default => 'Console', }; require __DIR__ . '/../views/layout.php'; diff --git a/examples/crash_reporter/backend/scripts/ingest_roadmap_tickets_20260604.php b/examples/crash_reporter/backend/scripts/ingest_roadmap_tickets_20260604.php new file mode 100755 index 0000000..6c42ec5 --- /dev/null +++ b/examples/crash_reporter/backend/scripts/ingest_roadmap_tickets_20260604.php @@ -0,0 +1,111 @@ +#!/usr/bin/env php + */ +function roadmap_tags(string $status): array { + $statusMap = [ + 'open' => ['id' => 'open', 'label' => 'open', 'bg' => '#22c55e'], + 'in-progress' => ['id' => 'in-progress', 'label' => 'in progress', 'bg' => '#3b82f6'], + 'closed' => ['id' => 'closed', 'label' => 'closed', 'bg' => '#6b7280'], + ]; + return [ + ['id' => 'ticket', 'label' => 'ticket', 'bg' => '#6366f1'], + ['id' => '20260604', 'label' => '20260604', 'bg' => '#0ea5e9'], + $statusMap[$status] ?? $statusMap['open'], + ]; +} + +/** title + body are the same description text */ +$tasks = [ + ['key' => 'bug-ticket-detail-500', 'status' => 'closed', 'text' => 'ISSUE: Ticket detail page HTTP 500 (?view=ticket&id=N). Fix recursion in comment/attachment loaders when rendering detail.'], + ['key' => '8', 'status' => 'closed', 'text' => 'COMPLETED: Orchestration branch feature/20260527_orchestration — merged to next; work lives under orchestration/; close/delete branch.'], + ['key' => '1', 'status' => 'open', 'text' => 'BE/builder deployment phases — dedicated services on BE; WIP verifying (Docker, out/builds, runner_script).'], + ['key' => '2', 'status' => 'open', 'text' => 'OTA checks — part of builder/deploy path; verify artifacts and device OTA flow.'], + ['key' => '3', 'status' => 'open', 'text' => 'Opus/Speex validation — E2E, encryption, FEC, NACK, negotiations; user and developer controls.'], + ['key' => '4', 'status' => 'open', 'text' => 'RSSH/WG (WireGuard v1) — finish deploy, prod validations, minimal tests; RA E2E CLI + Android poll.'], + ['key' => '5', 'status' => 'open', 'text' => 'Data visualization / graphs — WIP; issues found on drill-down and layout.'], + ['key' => '5.1', 'status' => 'open', 'text' => 'ISSUE 5.1: Graph brick click must open full-browser detailed chart (axis, legend, hover per point like Java visualizer).'], + ['key' => '5.2', 'status' => 'open', 'text' => 'REQUIREMENT 5.2: All graph preview bricks clickable; Columns combo (1/2/4) after Window default 2; grid uses full width.'], + ['key' => '5.3', 'status' => 'closed', 'text' => 'ISSUE 5.3: Remove Direct DB metrics / graph_upload / graphs.php footnote from graphs page.'], + ['key' => '6', 'status' => 'open', 'text' => 'Landing page checks, fixes, improvements — hub, styling, integration with dashboards.'], + ['key' => '6.1', 'status' => 'open', 'text' => 'Landing 6.1: Hub dashboards for graphs, builder, remote access — partially done.'], + ['key' => '6.1.1', 'status' => 'closed', 'text' => 'ISSUE 6.1.1: Remote access / graphs quick links — shell nav icons and 8px spacing.'], + ['key' => '6.1.2', 'status' => 'closed', 'text' => 'ISSUE 6.1.2: Remove device poll / heartbeat dev footnote from Remote access page.'], + ['key' => '6.2', 'status' => 'open', 'text' => 'Landing 6.2: Globe/earth animations — choose base on next; branch cleanup (3d_earth vs 20260531_globe_animation); clock/footer styling.'], + ['key' => '6.3', 'status' => 'open', 'text' => 'Landing 6.3: Mobile device validations (mobile chrome) — LOW priority.'], + ['key' => '7', 'status' => 'open', 'text' => 'BE roles RBAC — admin/root, slug admins, privilege sets; finish after related pages stable.'], + ['key' => 'net-slowness', 'status' => 'open', 'text' => 'ISSUE: Sporadic FE/BE slowness (SSH, sshfs, web) — WAN/modem queueing suspected; investigate bufferbloat and paths.'], +]; + +$device = [ + 'manufacturer' => 'roadmap', + 'model' => 'planning', + 'sdk_int' => 34, + 'release' => '14', +]; +$app = ['package' => 'com.foxx.androidcast', 'version_name' => 'roadmap', 'version_code' => 1]; +$now = (int) round(microtime(true) * 1000); +$ok = 0; +$fail = 0; + +foreach ($tasks as $task) { + $text = trim($task['text']); + $key = $task['key']; + $status = $task['status']; + $payload = [ + 'schema_version' => 1, + 'ingest_kind' => 'ticket', + 'ticket_id' => 'roadmap-20260604-' . preg_replace('/[^a-z0-9.-]+/i', '-', $key), + 'title' => mb_substr($text, 0, 256), + 'brief' => mb_substr($text, 0, 512), + 'body' => $text, + 'opened_at_epoch_ms' => $now, + 'rating' => $status === 'closed' ? 1 : 3, + 'owner' => $owner, + 'tags' => roadmap_tags($status), + 'device' => $device, + 'app' => $app, + ]; + $json = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + if ($dryRun) { + echo "DRY {$key} [{$status}]\n"; + $ok++; + continue; + } + $ctx = stream_context_create([ + 'http' => [ + 'method' => 'POST', + 'header' => "Content-Type: application/json\r\n", + 'content' => $json, + 'ignore_errors' => true, + ], + ]); + $resp = @file_get_contents($uploadUrl, false, $ctx); + $code = 0; + if (isset($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $m)) { + $code = (int) $m[1]; + } + if ($code === 200 || $code === 409) { + echo ($code === 409 ? 'DUP ' : 'OK ') . "{$key}\n"; + $ok++; + } else { + echo "FAIL {$key} HTTP {$code} — {$resp}\n"; + $fail++; + } +} + +echo "Done: ok={$ok} fail={$fail}\n"; +exit($fail > 0 ? 1 : 0); diff --git a/examples/crash_reporter/backend/src/TicketAttachmentRepository.php b/examples/crash_reporter/backend/src/TicketAttachmentRepository.php index 2a4b71c..20a125b 100644 --- a/examples/crash_reporter/backend/src/TicketAttachmentRepository.php +++ b/examples/crash_reporter/backend/src/TicketAttachmentRepository.php @@ -22,9 +22,7 @@ final class TicketAttachmentRepository { } public static function listForTicket(int $ticketId): array { - if (!TicketRepository::getById($ticketId)) { - return []; - } + // Do not call TicketRepository::getById here — avoids recursion from mapDetailRow. $pdo = Database::pdo(); $stmt = $pdo->prepare( 'SELECT * FROM ticket_attachments WHERE ticket_id = ? ORDER BY created_at_ms ASC, id ASC' diff --git a/examples/crash_reporter/backend/src/TicketCommentRepository.php b/examples/crash_reporter/backend/src/TicketCommentRepository.php index f420c68..1f82d80 100644 --- a/examples/crash_reporter/backend/src/TicketCommentRepository.php +++ b/examples/crash_reporter/backend/src/TicketCommentRepository.php @@ -3,10 +3,7 @@ declare(strict_types=1); final class TicketCommentRepository { public static function listForTicket(int $ticketId): array { - $ticket = TicketRepository::getById($ticketId); - if (!$ticket) { - return []; - } + // Do not call TicketRepository::getById here — mapDetailRow already loads comments. $pdo = Database::pdo(); $stmt = $pdo->prepare( 'SELECT id, ticket_id, author_username, body, created_at_ms, updated_at_ms diff --git a/examples/crash_reporter/backend/views/layout.php b/examples/crash_reporter/backend/views/layout.php index 12718cd..0c93728 100644 --- a/examples/crash_reporter/backend/views/layout.php +++ b/examples/crash_reporter/backend/views/layout.php @@ -202,6 +202,7 @@ + diff --git a/examples/crash_reporter/backend/views/partials/console_quick_links.php b/examples/crash_reporter/backend/views/partials/console_quick_links.php index 48702b7..6aa3f3b 100644 --- a/examples/crash_reporter/backend/views/partials/console_quick_links.php +++ b/examples/crash_reporter/backend/views/partials/console_quick_links.php @@ -9,7 +9,7 @@ $proj = '/app/androidcast_project'; $navClass = 'console-quick-links graphs-quick-links' . (isset($extra_class) ? ' ' . $extra_class : ''); $items = [ ['Console home', $bp . '/?view=home', 'nav-icon--home'], - ['Crash reports', $bp . '/?view=reports', 'nav-icon--reports'], + ['Issues', $bp . '/?view=reports', 'nav-icon--reports'], ['Tickets', $bp . '/?view=tickets', 'nav-icon--tickets'], ['Graphs', $proj . '/graphs/', 'nav-icon--graphs'], ['Builder', $proj . '/build/', 'nav-icon--builder'], diff --git a/examples/crash_reporter/backend/views/ticket_detail.php b/examples/crash_reporter/backend/views/ticket_detail.php index cee4bb4..c46c460 100644 --- a/examples/crash_reporter/backend/views/ticket_detail.php +++ b/examples/crash_reporter/backend/views/ticket_detail.php @@ -4,7 +4,7 @@ $p = is_array($ticket['payload'] ?? null) ? $ticket['payload'] : []; $device = is_array($p['device'] ?? null) ? $p['device'] : []; $app = is_array($p['app'] ?? null) ? $p['app'] : []; $pageTitle = 'Ticket'; -$view = 'tickets'; +$view = 'ticket'; $jsonFlags = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE; $rawJson = safe_json_encode($p, $jsonFlags); $bp = Auth::basePath();