mirror of
git://f0xx.org/android_cast
synced 2026-07-29 06:58:51 +03:00
sync BE
This commit is contained in:
336
docs/20260610_SERVICES.md
Normal file
336
docs/20260610_SERVICES.md
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
# Android Cast — exposed services catalog
|
||||||
|
|
||||||
|
_Validated: 2026-06-10 after FE `apps.conf` consolidation + BE sync. Public base: **https://apps.f0xx.org**._
|
||||||
|
|
||||||
|
**Documentation index:** [README.md](README.md) · Companion: [INFRA.md](INFRA.md), [20260608_BE_SERVICES_and_infra.md](20260608_BE_SERVICES_and_infra.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
<!-- toc -->
|
||||||
|
- [Validation snapshot](#validation-snapshot)
|
||||||
|
- [Hub landing](#hub-landing)
|
||||||
|
- [Console home](#console-home)
|
||||||
|
- [Issues (crash reports)](#issues-crash-reports)
|
||||||
|
- [Tickets](#tickets)
|
||||||
|
- [Graphs](#graphs)
|
||||||
|
- [Remote access](#remote-access)
|
||||||
|
- [RBAC / access control](#rbac--access-control)
|
||||||
|
- [APK builder](#apk-builder)
|
||||||
|
- [Git (Gitea)](#git-gitea)
|
||||||
|
- [OTA updates](#ota-updates)
|
||||||
|
- [Heartbeat (device)](#heartbeat-device)
|
||||||
|
- [Auth & account security](#auth--account-security)
|
||||||
|
- [Non-HTTP services](#non-http-services)
|
||||||
|
- [Validation scripts](#validation-scripts)
|
||||||
|
<!-- /toc -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation snapshot
|
||||||
|
|
||||||
|
Run: `examples/crash_reporter/backend/scripts/validate_be_services.sh`
|
||||||
|
|
||||||
|
| Check | URL / target | 2026-06-10 |
|
||||||
|
|-------|----------------|------------|
|
||||||
|
| Hub | `/app/androidcast_project/` | ✅ HTTP 200 |
|
||||||
|
| Crashes console | `…/crashes/` | ✅ HTTP 302 → login |
|
||||||
|
| Graphs | `…/graphs/` | ✅ HTTP 302 → login |
|
||||||
|
| Builder | `…/build/` | ✅ HTTP 302 → login |
|
||||||
|
| Git | `…/git/` | ✅ HTTP 200 |
|
||||||
|
| OTA channel | `/v0/ota/channel/stable.json` | ⚠️ HTTP **404** (FE proxy OK; artifact or BE `location` missing) |
|
||||||
|
| Crash upload | `…/crashes/api/upload.php` GET | ✅ HTTP **400** (expects POST JSON) |
|
||||||
|
| Heartbeat | `…/crashes/api/heartbeat.php` GET | ✅ HTTP **405** (expects POST) |
|
||||||
|
| Graph upload | `…/graphs/api/graph_upload.php` GET | ✅ HTTP **400** (expects POST) |
|
||||||
|
| Two-factor | `…/crashes/two-factor` | ✅ HTTP 302 → login (no pending session) |
|
||||||
|
|
||||||
|
**Alerts — may need follow-up patch**
|
||||||
|
|
||||||
|
1. **OTA:** Public returns **404**; BE direct `curl -H 'Host: apps.f0xx.org' http://artc0…/v0/ota/channel/stable.json` returned HTML lander redirect (not JSON). Confirm BE `listen 80` has `location ^~ /v0/ota/` **before** catch-all, and publish `stable.json` under `ota-artifacts/v0/ota/channel/`.
|
||||||
|
2. **2FA:** Fixed in repo — `completeTotpLogin()` no longer clears pending session on wrong code; login redirects to `/two-factor` when pending. **Sync BE** (`Auth.php`, `index.php`, `two_factor_challenge.php`, `app.css`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hub landing
|
||||||
|
|
||||||
|
**Web:** [https://apps.f0xx.org/app/androidcast_project/](https://apps.f0xx.org/app/androidcast_project/)
|
||||||
|
|
||||||
|
Marketing multipage shell; left nav links to Console, Git, Issues, Tickets, Graphs, Remote access, Builder. Shared session cookie `ac_crash_sess` path `/app/androidcast_project`.
|
||||||
|
|
||||||
|
| Kind | Path | Notes |
|
||||||
|
|------|------|--------|
|
||||||
|
| Static/PHP | `index.php` | Hub landing |
|
||||||
|
| CSS/JS | `hub.css`, `hub-landing.css`, `assets/hub-*.js` | Theme + i18n |
|
||||||
|
| Crashes assets | `…/crashes/assets/css/app.css` | Shared console theme |
|
||||||
|
|
||||||
|
No device ingest on hub root.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Console home
|
||||||
|
|
||||||
|
**Web:** [https://apps.f0xx.org/app/androidcast_project/crashes/?view=home](https://apps.f0xx.org/app/androidcast_project/crashes/?view=home) (auth required)
|
||||||
|
|
||||||
|
Landing cards: Issues, New issue, Tickets, New ticket, Graphs; quick links to all services.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issues (crash reports)
|
||||||
|
|
||||||
|
**Web:** [https://apps.f0xx.org/app/androidcast_project/crashes/?view=reports](https://apps.f0xx.org/app/androidcast_project/crashes/?view=reports)
|
||||||
|
|
||||||
|
Triage anonymous crash JSON; grouped/by-time views; tag editor; tree expand.
|
||||||
|
|
||||||
|
### Related endpoints
|
||||||
|
|
||||||
|
| Method | Endpoint | Auth | Purpose |
|
||||||
|
|--------|----------|------|---------|
|
||||||
|
| `POST` | `…/crashes/api/upload.php` | None | Device crash ingest (JSON or gzip JSON) |
|
||||||
|
| `GET` | `…/crashes/api/reports.php` | Session | List/filter reports (console JS) |
|
||||||
|
| `POST` | `…/crashes/api/report_tags.php` | Session | Update report tags |
|
||||||
|
| `POST` | `…/crashes/api/report_viewed.php` | Session | Mark report viewed |
|
||||||
|
| `GET` | `…/crashes/api/tag_catalog.php` | Session | Tag suggestions |
|
||||||
|
| `GET` | `…/crashes/api/diag.php` | Optional | Diagnostics (may 403 at edge) |
|
||||||
|
|
||||||
|
### Upload via curl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BASE=https://apps.f0xx.org/app/androidcast_project/crashes
|
||||||
|
SAMPLE=examples/crash_reporter/sample_crash_java.json
|
||||||
|
RID="manual-$(date +%s)"
|
||||||
|
jq --arg rid "$RID" '.report_id = $rid' "$SAMPLE" | \
|
||||||
|
curl -sS -X POST "$BASE/api/upload.php" \
|
||||||
|
-H 'Content-Type: application/json' --data-binary @-
|
||||||
|
```
|
||||||
|
|
||||||
|
Gzip upload smoke: `examples/crash_reporter/backend/scripts/test_gzip_upload.sh`
|
||||||
|
|
||||||
|
### Bulk / synthetic issues
|
||||||
|
|
||||||
|
- Optional payload fields: `ingest.source`, `ingest.client_id`, `ingest.run_id` — see [OPEN_API.md](OPEN_API.md).
|
||||||
|
- Loop `upload.php` with varied `report_id` / fingerprints for load tests.
|
||||||
|
- Tag harness traffic `synthetic` via console or `report_tags.php`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tickets
|
||||||
|
|
||||||
|
**Web:** [https://apps.f0xx.org/app/androidcast_project/crashes/?view=tickets](https://apps.f0xx.org/app/androidcast_project/crashes/?view=tickets)
|
||||||
|
|
||||||
|
Roadmap / QA tasks; workflow tags (`ticket` vs `issue` meta).
|
||||||
|
|
||||||
|
### Related endpoints
|
||||||
|
|
||||||
|
| Method | Endpoint | Auth | Purpose |
|
||||||
|
|--------|----------|------|---------|
|
||||||
|
| `GET` | `…/crashes/api/tickets.php` | Session | List tickets |
|
||||||
|
| `POST` | `…/crashes/api/ticket_create.php` | Session | Create ticket/issue |
|
||||||
|
| `POST` | `…/crashes/api/ticket_update.php` | Session | Update fields/tags |
|
||||||
|
| `POST` | `…/crashes/api/ticket_tags.php` | Session | Tag catalog ops |
|
||||||
|
| `POST` | `…/crashes/api/ticket_comments.php` | Session | Comments |
|
||||||
|
| `POST` | `…/crashes/api/ticket_upload.php` | Session | Attachment upload |
|
||||||
|
| `GET` | `…/crashes/api/ticket_attachments.php` | Session | List attachments |
|
||||||
|
| `GET` | `…/crashes/api/ticket_attachment.php` | Session | Download attachment |
|
||||||
|
|
||||||
|
### Create via UI
|
||||||
|
|
||||||
|
- Issues view: **New issue** → `ticket_create.js` → `ticket_create.php` with `issue` meta tag.
|
||||||
|
- Tickets view / home: **New ticket** → same API with `ticket` meta tag.
|
||||||
|
|
||||||
|
### Create via curl (session cookie)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
BASE=https://apps.f0xx.org/app/androidcast_project/crashes
|
||||||
|
JAR=/tmp/ac-cookie.txt
|
||||||
|
curl -sS -c "$JAR" -b "$JAR" -X POST -d 'username=admin&password=admin' "$BASE/login" -o /dev/null
|
||||||
|
curl -sS -b "$JAR" -X POST -H 'Content-Type: application/json' \
|
||||||
|
-d '{"title":"API test","brief":"from curl","body":"","tags":[{"id":"ticket","label":"ticket","bg":"#6366f1"},{"id":"open","label":"open","bg":"#22c55e"}]}' \
|
||||||
|
"$BASE/api/ticket_create.php"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Graphs
|
||||||
|
|
||||||
|
**Web:** [https://apps.f0xx.org/app/androidcast_project/graphs/](https://apps.f0xx.org/app/androidcast_project/graphs/)
|
||||||
|
|
||||||
|
Session metrics dashboards (user / slug / platform scopes); drag-reorder bricks; empty-session notice when no `graph_upload` data.
|
||||||
|
|
||||||
|
### Related endpoints
|
||||||
|
|
||||||
|
| Method | Endpoint | Auth | Purpose |
|
||||||
|
|--------|----------|------|---------|
|
||||||
|
| `GET` | `…/graphs/api/graphs.php?days=14` | Session | Aggregated metrics JSON |
|
||||||
|
| `POST` | `…/graphs/api/graph_upload.php` | Device/session | Ingest `graph_session` NDJSON/JSON |
|
||||||
|
|
||||||
|
Graphs PHP routes through crashes `public/index.php` (`?view=graphs`).
|
||||||
|
|
||||||
|
### Fake session data (dev / BE)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate NDJSON
|
||||||
|
php examples/crash_reporter/backend/scripts/generate_fake_graph_sessions.php \
|
||||||
|
--devices=100 --per-device=6 --out=/tmp/sessions.ndjson
|
||||||
|
|
||||||
|
# SQL import on BE (when HTTP upload times out)
|
||||||
|
php examples/crash_reporter/backend/scripts/ndjson_to_graph_sql.php /tmp/sessions.ndjson | \
|
||||||
|
ssh alpine-be 'mariadb androidcast_crashes'
|
||||||
|
```
|
||||||
|
|
||||||
|
API smoke: `examples/crash_reporter/backend/scripts/test_graphs_api.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Remote access
|
||||||
|
|
||||||
|
**Web:** [https://apps.f0xx.org/app/androidcast_project/crashes/?view=remote_access](https://apps.f0xx.org/app/androidcast_project/crashes/?view=remote_access) (RBAC: `remote_access_view`)
|
||||||
|
|
||||||
|
Operator console: whitelist devices, open WireGuard or RSSH sessions.
|
||||||
|
|
||||||
|
### Related endpoints
|
||||||
|
|
||||||
|
| Method | Endpoint | Auth | Purpose |
|
||||||
|
|--------|----------|------|---------|
|
||||||
|
| `POST` | `…/crashes/api/remote_access.php?action=…` | Session | `whitelist`, `open_session`, `dashboard`, … |
|
||||||
|
| `POST` | `…/crashes/api/heartbeat.php` | None | Device RA poll (`heartbeat.type=ra`) |
|
||||||
|
|
||||||
|
Device flow: app polls `heartbeat.php` → operator uses console → next poll returns `connect` payload.
|
||||||
|
|
||||||
|
Smoke: `CRASHES_BASE=https://apps.f0xx.org/app/androidcast_project/crashes examples/crash_reporter/backend/scripts/test_remote_access_api.sh`
|
||||||
|
|
||||||
|
Prod checklist: `examples/crash_reporter/backend/scripts/verify_remote_access_prod.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RBAC / access control
|
||||||
|
|
||||||
|
**Web:** [https://apps.f0xx.org/app/androidcast_project/crashes/?view=rbac](https://apps.f0xx.org/app/androidcast_project/crashes/?view=rbac) (admin)
|
||||||
|
|
||||||
|
### Related endpoints
|
||||||
|
|
||||||
|
| Method | Endpoint | Auth | Purpose |
|
||||||
|
|--------|----------|------|---------|
|
||||||
|
| `GET/POST` | `…/crashes/api/rbac.php` | Session | Roles, memberships, lockouts |
|
||||||
|
| `GET/POST` | `…/crashes/api/users.php` | Session | User admin |
|
||||||
|
|
||||||
|
Smoke: `examples/crash_reporter/backend/scripts/test_rbac_api.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## APK builder
|
||||||
|
|
||||||
|
**Web:** [https://apps.f0xx.org/app/androidcast_project/build/](https://apps.f0xx.org/app/androidcast_project/build/)
|
||||||
|
|
||||||
|
Docker CI: trigger builds, stream logs, OTA artifact option.
|
||||||
|
|
||||||
|
### Related endpoints
|
||||||
|
|
||||||
|
| Method | Endpoint | Auth | Purpose |
|
||||||
|
|--------|----------|------|---------|
|
||||||
|
| `GET` | `…/build/api/health.php` | Session | Docker/socket health |
|
||||||
|
| `GET` | `…/build/api/builds.php` | Session | Build history |
|
||||||
|
| `POST` | `…/build/api/build_trigger.php` | Session | Start build |
|
||||||
|
| `GET` | `…/build/api/build_log.php?id=` | Session | Log tail |
|
||||||
|
| `POST` | `…/build/api/build_actions.php` | Session | Cancel / rerun |
|
||||||
|
|
||||||
|
Shared login with crashes (`ac_crash_sess`). See [BUILD_DEPLOY.md](BUILD_DEPLOY.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Git (Gitea)
|
||||||
|
|
||||||
|
**Web:** [https://apps.f0xx.org/app/androidcast_project/git/](https://apps.f0xx.org/app/androidcast_project/git/)
|
||||||
|
|
||||||
|
FE proxies to `http://artc0.intra.raptor.org:3000/` (Gitea). Canonical git remote: `git://f0xx.org/android_cast`.
|
||||||
|
|
||||||
|
No Android Cast PHP on this path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OTA updates
|
||||||
|
|
||||||
|
**Web channel:** [https://apps.f0xx.org/v0/ota/channel/stable.json](https://apps.f0xx.org/v0/ota/channel/stable.json)
|
||||||
|
|
||||||
|
Device setting: `ota.base_url` in `examples/settings.json` → `https://apps.f0xx.org/v0/ota/channel/stable.json`.
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
|
||||||
|
See [OTA.md](OTA.md): `v0/ota/channel/{stable,staging,…}.json` → manifest → `.otapkg` / `.otabundle.zip`.
|
||||||
|
|
||||||
|
### Publish (operator)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/generate-ota-v0.sh app/build/outputs/apk/release/app-release.apk \
|
||||||
|
https://apps.f0xx.org ./out/ota staging
|
||||||
|
OTA_DEPLOY_TARGET=… ./scripts/deploy-ota-staging.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
BE mount: `…/androidcast_project/ota-artifacts/v0/` — `prepare-be-ota-mount.sh`.
|
||||||
|
|
||||||
|
**Status 2026-06-10:** FE `/v0/ota/` → BE :80 configured; channel file **not yet served** (404 / lander) — publish artifacts + verify BE nginx `location ^~ /v0/ota/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Heartbeat (device)
|
||||||
|
|
||||||
|
**Web:** none (app service only)
|
||||||
|
|
||||||
|
**Endpoint:** `POST https://apps.f0xx.org/app/androidcast_project/crashes/api/heartbeat.php`
|
||||||
|
|
||||||
|
JSON body with `heartbeat` object: session stats, remote-access poll (`type: ra`), etc.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sS -X POST -H 'Content-Type: application/json' \
|
||||||
|
-d '{"heartbeat":{"type":"ping","device_id":"test-device"}}' \
|
||||||
|
https://apps.f0xx.org/app/androidcast_project/crashes/api/heartbeat.php
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auth & account security
|
||||||
|
|
||||||
|
| Page | URL |
|
||||||
|
|------|-----|
|
||||||
|
| Login | […/crashes/login](https://apps.f0xx.org/app/androidcast_project/crashes/login) |
|
||||||
|
| Register | […/crashes/register](https://apps.f0xx.org/app/androidcast_project/crashes/register) |
|
||||||
|
| Two-factor challenge | […/crashes/two-factor](https://apps.f0xx.org/app/androidcast_project/crashes/two-factor) |
|
||||||
|
| Account security (TOTP enroll) | […/crashes/account-security](https://apps.f0xx.org/app/androidcast_project/crashes/account-security) |
|
||||||
|
| Email verify | `…/crashes/verify-email?token=` |
|
||||||
|
|
||||||
|
| Method | Endpoint | Purpose |
|
||||||
|
|--------|----------|---------|
|
||||||
|
| `POST` | `…/crashes/api/auth_register.php` | Registration API |
|
||||||
|
|
||||||
|
TOTP verify script: `examples/crash_reporter/backend/scripts/verify_auth_totp.php`
|
||||||
|
|
||||||
|
Flow doc: [20260607-2FA-email-mobile-auth-flow.md](20260607-2FA-email-mobile-auth-flow.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Non-HTTP services
|
||||||
|
|
||||||
|
| Service | Access | Used by |
|
||||||
|
|---------|--------|---------|
|
||||||
|
| **WireGuard** | `ra.apps.f0xx.org:45340/udp` | Remote access lab mode |
|
||||||
|
| **RSSH bastion** | `ra.apps.f0xx.org:443/tcp` | Reverse SSH alpha mode |
|
||||||
|
| **Cast LAN** | UDP/TCP `:41235`, discovery `:41234` | Screen mirror (no BE) |
|
||||||
|
| **MQTT OTA** (optional) | `mqtt://host:port/v0/ota/…` | Retained topic payloads |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation scripts
|
||||||
|
|
||||||
|
| Script | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `validate_be_services.sh` | Public HTTP smoke (this doc’s snapshot) |
|
||||||
|
| `test_gzip_upload.sh` | Crash upload plain + gzip |
|
||||||
|
| `test_graphs_api.sh` | Graphs JSON API |
|
||||||
|
| `test_remote_access_api.sh` | RA whitelist + session |
|
||||||
|
| `test_rbac_api.sh` | RBAC API |
|
||||||
|
| `test_rssh_api.sh` | RSSH mode API |
|
||||||
|
| `test_tags_api.sh` | Report tag API |
|
||||||
|
| `verify_remote_access_prod.sh` | Prod RA checklist |
|
||||||
|
|
||||||
|
FE nginx template: `examples/crash_reporter/backend/nginx/fe/apps.conf`
|
||||||
|
Regenerate FE patch: `scripts/generate-fe-nginx-patch.sh`
|
||||||
532
docs/20260610_SERVICES.pdf
Normal file
532
docs/20260610_SERVICES.pdf
Normal file
@@ -0,0 +1,532 @@
|
|||||||
|
%PDF-1.4
|
||||||
|
%“Œ‹ž ReportLab Generated PDF document (opensource)
|
||||||
|
1 0 obj
|
||||||
|
<<
|
||||||
|
/F1 2 0 R /F2 3 0 R /F3 32 0 R /F4 34 0 R /F5 35 0 R /F6 36 0 R
|
||||||
|
/F7 37 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
4 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 38 0 R /Fit ] /Rect [ 45.68504 716.2874 115.501 727.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 38 0 R /Fit ] /Rect [ 45.68504 705.2874 88.37304 716.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 38 0 R /Fit ] /Rect [ 45.68504 694.2874 97.26104 705.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 38 0 R /Fit ] /Rect [ 45.68504 683.2874 123.037 694.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
8 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 38 0 R /Fit ] /Rect [ 59.68504 672.8499 120.135 682.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
9 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 38 0 R /Fit ] /Rect [ 59.68504 662.8499 109.2825 672.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
10 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 39 0 R /Fit ] /Rect [ 59.68504 652.8499 133.4625 662.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
11 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 39 0 R /Fit ] /Rect [ 45.68504 642.2874 71.02104 653.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
12 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 39 0 R /Fit ] /Rect [ 59.68504 631.8499 120.135 641.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
13 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 39 0 R /Fit ] /Rect [ 59.68504 621.8499 103.4475 631.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
14 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 39 0 R /Fit ] /Rect [ 59.68504 611.8499 164.295 621.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
15 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 39 0 R /Fit ] /Rect [ 45.68504 601.2874 71.91704 612.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
16 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 39 0 R /Fit ] /Rect [ 59.68504 590.8499 120.135 600.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
17 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 39 0 R /Fit ] /Rect [ 59.68504 580.8499 155.9775 590.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
18 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 45.68504 570.2874 100.813 581.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
19 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 59.68504 559.8499 120.135 569.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
20 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 45.68504 549.2874 125.709 560.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
21 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 59.68504 538.8499 120.135 548.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
22 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 45.68504 528.2874 87.92504 539.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
23 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 59.68504 517.8499 120.135 527.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
24 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 45.68504 507.2874 82.58104 518.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
25 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 45.68504 496.2874 92.82104 507.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
26 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 59.68504 485.8499 82.20004 495.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
27 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 40 0 R /Fit ] /Rect [ 59.68504 475.8499 119.2875 485.8499 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
28 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 41 0 R /Fit ] /Rect [ 45.68504 465.2874 111.485 476.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
29 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 41 0 R /Fit ] /Rect [ 45.68504 453.6874 129.725 463.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
30 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 41 0 R /Fit ] /Rect [ 45.68504 443.2874 115.469 454.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
31 0 obj
|
||||||
|
<<
|
||||||
|
/Border [ 0 0 0 ] /Contents () /Dest [ 41 0 R /Fit ] /Rect [ 45.68504 432.2874 106.149 443.2874 ] /Subtype /Link /Type /Annot
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
32 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica-Oblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
33 0 obj
|
||||||
|
<<
|
||||||
|
/Annots [ 4 0 R 5 0 R 6 0 R 7 0 R 8 0 R 9 0 R 10 0 R 11 0 R 12 0 R 13 0 R
|
||||||
|
14 0 R 15 0 R 16 0 R 17 0 R 18 0 R 19 0 R 20 0 R 21 0 R 22 0 R 23 0 R
|
||||||
|
24 0 R 25 0 R 26 0 R 27 0 R 28 0 R 29 0 R 30 0 R 31 0 R ] /Contents 74 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 73 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0
|
||||||
|
/Trans <<
|
||||||
|
|
||||||
|
>> /Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
34 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Courier /Encoding /WinAnsiEncoding /Name /F4 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
35 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica-BoldOblique /Encoding /WinAnsiEncoding /Name /F5 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
36 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /ZapfDingbats /Name /F6 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
37 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Symbol /Name /F7 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
38 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 75 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 73 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
39 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 76 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 73 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
40 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 77 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 73 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
41 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 78 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 73 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
42 0 obj
|
||||||
|
<<
|
||||||
|
/Outlines 44 0 R /PageMode /UseNone /Pages 73 0 R /Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
43 0 obj
|
||||||
|
<<
|
||||||
|
/Author (Android Cast project) /CreationDate (D:20260610172108+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260610172108+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
|
/Subject (\(unspecified\)) /Title (Android Cast \204 exposed services catalog) /Trapped /False
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
44 0 obj
|
||||||
|
<<
|
||||||
|
/Count 35 /First 45 0 R /Last 72 0 R /Type /Outlines
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
45 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 38 0 R /Fit ] /Next 46 0 R /Parent 44 0 R /Title (Validation snapshot)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
46 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 38 0 R /Fit ] /Next 47 0 R /Parent 44 0 R /Prev 45 0 R /Title (Hub landing)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
47 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 38 0 R /Fit ] /Next 48 0 R /Parent 44 0 R /Prev 46 0 R /Title (Console home)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
48 0 obj
|
||||||
|
<<
|
||||||
|
/Count 3 /Dest [ 38 0 R /Fit ] /First 49 0 R /Last 51 0 R /Next 52 0 R /Parent 44 0 R
|
||||||
|
/Prev 47 0 R /Title (Issues \(crash reports\))
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
49 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 38 0 R /Fit ] /Next 50 0 R /Parent 48 0 R /Title (Related endpoints)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
50 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 38 0 R /Fit ] /Next 51 0 R /Parent 48 0 R /Prev 49 0 R /Title (Upload via curl)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
51 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 39 0 R /Fit ] /Parent 48 0 R /Prev 50 0 R /Title (Bulk / synthetic issues)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
52 0 obj
|
||||||
|
<<
|
||||||
|
/Count 3 /Dest [ 39 0 R /Fit ] /First 53 0 R /Last 55 0 R /Next 56 0 R /Parent 44 0 R
|
||||||
|
/Prev 48 0 R /Title (Tickets)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
53 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 39 0 R /Fit ] /Next 54 0 R /Parent 52 0 R /Title (Related endpoints)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
54 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 39 0 R /Fit ] /Next 55 0 R /Parent 52 0 R /Prev 53 0 R /Title (Create via UI)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
55 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 39 0 R /Fit ] /Parent 52 0 R /Prev 54 0 R /Title (Create via curl \(session cookie\))
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
56 0 obj
|
||||||
|
<<
|
||||||
|
/Count 2 /Dest [ 39 0 R /Fit ] /First 57 0 R /Last 58 0 R /Next 59 0 R /Parent 44 0 R
|
||||||
|
/Prev 52 0 R /Title (Graphs)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
57 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 39 0 R /Fit ] /Next 58 0 R /Parent 56 0 R /Title (Related endpoints)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
58 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 39 0 R /Fit ] /Parent 56 0 R /Prev 57 0 R /Title (Fake session data \(dev / BE\))
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
59 0 obj
|
||||||
|
<<
|
||||||
|
/Count 1 /Dest [ 40 0 R /Fit ] /First 60 0 R /Last 60 0 R /Next 61 0 R /Parent 44 0 R
|
||||||
|
/Prev 56 0 R /Title (Remote access)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
60 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 40 0 R /Fit ] /Parent 59 0 R /Title (Related endpoints)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
61 0 obj
|
||||||
|
<<
|
||||||
|
/Count 1 /Dest [ 40 0 R /Fit ] /First 62 0 R /Last 62 0 R /Next 63 0 R /Parent 44 0 R
|
||||||
|
/Prev 59 0 R /Title (RBAC / access control)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
62 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 40 0 R /Fit ] /Parent 61 0 R /Title (Related endpoints)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
63 0 obj
|
||||||
|
<<
|
||||||
|
/Count 1 /Dest [ 40 0 R /Fit ] /First 64 0 R /Last 64 0 R /Next 65 0 R /Parent 44 0 R
|
||||||
|
/Prev 61 0 R /Title (APK builder)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
64 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 40 0 R /Fit ] /Parent 63 0 R /Title (Related endpoints)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
65 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 40 0 R /Fit ] /Next 66 0 R /Parent 44 0 R /Prev 63 0 R /Title (Git \(Gitea\))
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
66 0 obj
|
||||||
|
<<
|
||||||
|
/Count 2 /Dest [ 40 0 R /Fit ] /First 67 0 R /Last 68 0 R /Next 69 0 R /Parent 44 0 R
|
||||||
|
/Prev 65 0 R /Title (OTA updates)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
67 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 40 0 R /Fit ] /Next 68 0 R /Parent 66 0 R /Title (Layout)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
68 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 40 0 R /Fit ] /Parent 66 0 R /Prev 67 0 R /Title (Publish \(operator\))
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
69 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 41 0 R /Fit ] /Next 70 0 R /Parent 44 0 R /Prev 66 0 R /Title (Heartbeat \(device\))
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
70 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 41 0 R /Fit ] /Next 71 0 R /Parent 44 0 R /Prev 69 0 R /Title (Auth & account security)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
71 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 41 0 R /Fit ] /Next 72 0 R /Parent 44 0 R /Prev 70 0 R /Title (Non-HTTP services)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
72 0 obj
|
||||||
|
<<
|
||||||
|
/Dest [ 41 0 R /Fit ] /Parent 44 0 R /Prev 71 0 R /Title (Validation scripts)
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
73 0 obj
|
||||||
|
<<
|
||||||
|
/Count 5 /Kids [ 33 0 R 38 0 R 39 0 R 40 0 R 41 0 R ] /Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
74 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1005
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
Gau1.;,>q#&BE]('_aGL8VG`QPB:_[<sBSs(^-E[9l=F=f/1ch#KF71-&'2Q0Gf#7PY+Z=s3L_WZgS0Qp`]UD=94A%fKUKD.--^[iZO!R(oI,ScQ.'#AB]WN@pod[RKd1WN:DCB3s[V\Ym>qpR"T/X6-ACF-D7Pcn==)7P<uuM#mu?.&sRP@,c!^o_[,_o,n*7cWKf#C_Ms6eV(%4-NA@42,P8ApKRn_Z?E'u2aF?JS*qoS\!s3ticZ@G>$.o>:$ct(GnFZT,F;1r:2X1"aR'?JH(^AG*\CT.dUrcr1+P]-C.psn;BU8@"cT_u'bldBWPAoP@IIMTFo6DWI>sb6".r&i>!&5G$f8o?JbBi&:YC=;<=`Q#m%Ar9I=X`U/+%F!:&)0*D]ImcDol&Ys8:B_t<,E?G^/-?=+Ip+ki6M`P):N&8c3B!bAcZ'l8m=++Fb!#lLEJ=u7g0R1Fij8$VkW/d$V&WWUSLiJ-&Gu^o;5EuKPS]'BQ`D:_#+7mgM.&l?uBbl&69PhqsZRq`1aAnaCVUj:@:X,C\gd+l5*m'"X<RFE.6jj:]?i2Q[`,&7MsTkL4rMu.9t#Y%>VYQiKNEK_BB7;gKr<L,-]QM_?"!N\2PK?,`],X`AQ6/Gj5us,kDb);mB5f'YM4._Slhi]s@*7`2U5bO%0j?ID,Ei-.0kD?Y7^Z"MRU'M(]N3-_Fi(b,Q006'lDkl[.k>o<nfoD"G9>i%ARW&&!+[?h4l:%@4`OnJc,qlb7!-0u$_j6g#DdIW\H?GpT^)S./FB1q-(=\XrT^^BN54Ni1f]^W"%2$%P0V`;`k@^O2gL1*P4PCDZKCgMg+r><DuV+I;`PJKaa<PCHI&hI3"JCh?0Bof)MP%O5)LA`H\Y0o^g=AE:]A29$5jgcb.]iAu9?>b@ur(ZIeH7YfWMI6X3m?5u\:B>LTQ&(h`/IL2:@QNh>l?@o9;jPp8a=5l\_"*T5uB%j+$Gjq:>]&jas\l^@tJgY>gS_pKqgsu~>endstream
|
||||||
|
endobj
|
||||||
|
75 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 3476
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
Gb!#_=``=W&q9SYkaIs>;%fAXI5mMGb2L:&E=u'Cm(U!\"];MBQm+Nnj^TD+[r6nc8;[YNA27AL,Coh6NNX:\mRCC])1RnCIs2"aC(A_%$7ICJDa;\r*Te/W2F%'08BFBeL1K"mXFk*=P+als^5(P>U/KFf,Qm0J:h9QNJJ9uG3'kZJ'us;I53!AOr^hKU8J8Ts"]\UJ'A5KFA0<f;NT'7B4G!<0,c#(+-7"J9jG(KFr]Hf\!r._..4$VUi#00g"83SA&mT;j^?<@b0R?o!1\/$+8J8;Wkqo5_,<n:]@D$>OQI>L).7`D/r+SU<O=XPD3$K@F<6;RZV0d_9QNJ.n.LLr1<AQnj[!e/3b#SB7W`_S2[LNrON\sb`g?2D(1/u&lchMc)S2TIq+35f".@[n'@I*6H7U_5gS1]VE97u>j\ZlqsCjMF@qg(Q3)3+)IQSOE"4#o398_+RX$sTnh/BP?#)&c&7A'1)HP%CSPcLpEMi3+"J0%H(`75a$ld3,<*o+Yp0T43cA[#L2GJiY_QMmmZp+D#qJ!+f:SUhj6?&@V5R9;k<3FPGamT\GK6NG^g[6U8#Q=40ePQ'uEXU@)/5%?4Gl,_a)ne=.t48\s?g-El%N^$Scb`@rUkP+=rgJ,,$b#SIcRB_S[ZA4Ju?efb^n9P/p)Pm$!^4b0N?^oU.Nf(88q!%oG+HBg*A`)@:"j3V#AKI,5I*.F`%1)7TBoI?_Vc0F(MDUE2,L%G-<'$mC$\i'E44H7;J2)g\_jbFTd^f"j+)O=I)Ch2*6Xg.qUUK8V!T-gVXH"#6+1sFO[r,Ca$I?5\iOc[>98[N(28.1R:7s^HK(BJh=mscS/[PJ]^='O!XdWcUB+k$6I00"@\Qq)5s2kk,VUUAe@5#+_'Kug]9Z6s+"8+ZYlbcRk("maY8-+I_'"OnLt9o+2qd(5F=@7$je'N:<ZKhnPK/F8LE!Me$X7V+3e;n1^cc2DP>fE5[@P<00d?.S=O-ZCrZ&.9$%LI_.3qf<m"inY/)JM>(DWo/?hjnka@7N+uV)il^,=C")SOdhGNZ[YTa2Y'5@*#>($)U)02e/L[(:Il+DdqgK_>"?g@^,qepbAr\s+]48]UqA?Pq@K@$`8L48C_#oHT0Dt+irH!<U3\@=&[4A$nSaN.\sVkmE==Vm*Q;IZMka>dj5%c*QP&'k"oXS#a;l0OMtH?.2su\l2LaHLkhd4eV(o^q:Und\"e;1%I*5u+,f;G*[/^0MrBV"u+[e.hi^!U(idh[GXb(eY^L*$I>IAQ?J`c.YG=RespLTq/Zca-RV@U#?9<NHZ[E!<!?tDk6+eC']G%%`D_KN<pJ'0H5X*%jWm#p[UaZJp,X4i3.U;PE[*S;i&l2Xf9Y&dlW]$>A^jZ:Ec_ht.!Wlqq*)`r^0fut.jpQpl6s,X;cUg^;:7RDE67b"XVaik!3Wc=?)j?#S#;&218a\/8G\C[o`'bMaQ&5]rkS'^oIqibk"&P:dGPiIc->qUD=g9&%^pKj.bO`kmsmG`TYBt6O87n%S^VTC_+=S%NPa-Jl6c4er*01K0s9Yn<pfS>Z3q"6a[aXP_[g:m\bFn8:NS@ETS<!L"d$BH'GLuA+,_aOU\`$YT*H.\O+_tSA<q]-<^%YG]RhJd?OD#,2GBE5KrS7YLJhUpW@KRa"<$AP%R2'?,MRkU_a5q\N!$1[t(5dhiqW2":aR'$[HD,:ORGAeTk-\^$nRZKF@#[AVlMUSDA\0:bB:#48Wp$KK-^%EFCOD'Z"L-F?79f39',!Vdm0SqEljh@2EB"C;59t;>M/!Rg7USGhtL)od@I>fDd%@?^4IKU@r_B9/Sdl%`Gb%16^>"2DRI%n<nc5m(sLfl4r?;2@0]\Fk"RLbSk4Ds`MTtha<"sVsV);j$oGi,D)Mo^7T5:)Z$`\?10,&1RZWbHV52sIbW%N'J^FM;#\hqEnV0I+0n865UMHe1f';do\p(OPH#H6G42/%f@g\%<Isge](`HqP-Ue=3S4GS+(U5W?p];4ZrG!Wfh6h[==#D18$eEf]1MQQ3n]N,:"*L:QIk$2frt+-5A:h1Q(<ijuWqNZh2a8MF?^g`5GDEPq:sU\j2.\C]F4)B[VM%SsJqMq/'&Y\>TTh_a)f!',#uAh&F76b7]eQ'3qIQ1_8g7ts/"#3L-:'Z5t2Ge04i:4`>jBY#9_!LnB(lJ)9(7@H(l4IRR@KY,^Vo?1R,KW*M^UUll]Kk`a9,.8$b`dT#;/[71&:#F8,N8/3j_7e3D]j9,_'s%<&_-n_eb1_TZ=<>K/OA5Ot:XMH@Ld+D)11/K>KkK<mG$hP&FF7D"[Ui,,X(1cA-j4Zr4lO.kX?OaeMu<D_&rta+7FGkS/&<=9K];39>^I;n+bSpkdXeMU:,Qd+Wc;-#>0bV=NTT&f_5LrbP0rMfBi]_8Xp@@Z7M,DNKI,^`0'I2V-qh'GN;Ek>0MDV>4O(_Kfeg8bA;uABB$UWI\!*/*/$KLfoqja3MVm,8q@R%?Z*XFLkL'/CS7V?X$+fYO\Gs+YJ7@2qceC$<0e-jr']hbq.PZm@QgD)$mlo0HnG)a8VoPDUBqt1oSca#thX5b[$kf57I1kLDPr)5'p@_Kj/dI>7KPVrjB*pD)AQqh2:Xq9I]7&*LY7s"S!aH[6"fd\g0TS@<"-=W6*]N&D5mA6'Gs$j^*,["4S1YjY&eA!,o7`o7a9jWd`TRlJa-6XRLi>KY@TPF`9<*.FY[,'1n-??64mMX%9#+gp$GE);R_lmBjQ^hS"a4Wr-!I&PY-mb8^r:MP/,(UX!4mnB(co#o(OpS8RNcd5??>$hct"0jKH,jqfdP998>##/dRd]o<6:BLKD=SsQYp/$[j57&oKrmkDW<-Uc%A\PVGZF%GQ.LU8O*@;#nPi`pBsE'5,B;0#2L=c.28]SfZXhn#2qM/l][aXg]>!TE*LGB$Wd;tb4SF4n#C5edX`<qI5d=cGfGs'P>6SrLl"W8^:Kr/jb&9k1nO>na-,\Hk9jsR5@"G:"*2HOrN''Ck30r_Ymk48(2Xe4-6eUg4F#%:D^$MMTiE*[TCJ_l7(c2W(Ca>FdoK[/]$a5F*rROO;=`ZE:alo)B[DF`FdPS:PNM]!66:M3[/17I9F?b<=oG`P@KJi$\opY3r,=N6K8e.@#`I_nF:iiMCqJ[3$dR,.4%:T>KLG?iBd+80'#P#Ai('+r7N<,j?159*IOs_BaE8%<kUo(Rq!GYQD?i`ebZW>3/a-$5H:<N6=O9nTlmV(r&E7\$XYaNh:#XU%.@a[$KiP1NY]eP27ZCcm3]sUBka>VNH**gGj',NS9GMf%g">t5\t7#"9md\8U;&MUjZ&E>WfGR"LM=@$USH0hWUG_aOoR.1&@emLGI.6g)=sp,%7r78]d,>s13ut5YN%@,EF<FEkV$R]0+Q,SP\+q/o#'S.fo*!tDI_qkr(?B-\KeMVhoY'=hRrVt6\6o[:O^$HV!%Q[?eEnmT5$p4dJj3s/6MZ~>endstream
|
||||||
|
endobj
|
||||||
|
76 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2874
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
Gb!;fHZ.Xu')t1g@c6WZdRC8Cos1n0p<kkR:!mh,`euq:E\M0`[["ps.a!%l?GH@H-!?NKaemkS*[l2AMU]:f_r8g93b\:aci3Ac!+(^OrC$s^^h=(dWL,C9!,&'ZY6TBYd^0^8?tgrINhpF/d)>YgrcF:,Bp:/^RPO>`-M0RHQ7'!@Uo9VRF@WTDZp5ZZf])'.#(jb?oiLAOBek=ZpRq9?bPBm6*%D8QUEgl?UD%m'1P[r4H2?:7LU/'"$a-X=_WVn-5Je"_]1`:io1S<I%5'hMNc=+Ge&sP4`kK$CK8=hG4HFiY0*1]pKu5iHR'JF5NuhbR$oEtHhkd:oL$COV_F#$]UFGS+r/`69n<a-3!1N"jZXJ[L34^=Ce%EIC$7Qa<h)#\bmA"IVmtV=0J[!SFS8e,gT7.3Z7_fhf%>pY^,Y[KZV+9mD0&hD$eP.HaO)lPO7%I0KL4u5Ah8G@QY,7/Uiu+"!(C&mj%FpnY2[4-)%gptp\`<mJ3PoRb?S@$N:k:ial("Ta$U3`H`[/,-ZS^nN=*2sJ-G.\H#<p*.;Aqeo+#;kNI27)*OnJWkL3?F=ZqO_*bS:hQ?F;>-"!8NhrD[fcZ4%g.L+>S1i/GLMMmJK)du/9Yi@,DJ_/7n^LqIRg6r@2*FhMMSEo-WV*,]KM1U$dU\RFgmW+^(u4c'h:9K?L^J.)S>K3Ul;c1kTM(ss5FBbE2H+\h0BMZ-Zj^N@]2ppWEj2[XgE0L4$T[8,tF2qUbq4&$S2m/b,5#HR[ST$qhU3[(OU-cT027m?A:<+:X9('aY*(6?[4lK8$qRiL$32G_eh+0-6.A$/M5qg0V]An%Racq>S?F"'SN3/Of+S*#h?,M(2M5.`mJV1gge(;&r,M(e(A$Q>dQIQiIMnf.QAgdT?01l1Qb5RCL^24S`u*r-"i[*(m;X"R\]P[;%fE!BMg[OAOF^j]\m`=j(Bbf>juBs$aKUGP?I^bkC82anYs__OJ3L:64Q5]J>11j*esLZ5FeL11W-U(\LB\cE+il&SA5r*-;.SYe8:7aQhbhpkqs=a:D2\V[UXRb$i"XD9mH%gI@l[dBW-bsnOQcm=ZTQ?9G9NT?+sP=O;oJH-+"0UjjPP,DXjKjg9prPm92jL)RVN`k*:SRinkB8%S=&gml\CsCg0maGb$T!9-0UR2ssVgAuh==/%+=cIUtIBg&d?sqY#/BoZfG@r-,!4pXm,+;Md=-3n-_6lD([Y#dC:p2Xe`:F]W/I>[ne,J:s'R$#W.(*c223TF+,j]H:gBcttYoEaR2ds'E78bIhLQlCtimF=j)q6$B"X<7.fP?%lkg'A>DSi<'AJoiA^`oW.T!3=7rB':pYiGtJ.WUq/W!m73iuXj%[3rkfWkjf;,#VV3#=s'eU@N8;8qUd];7m>Ha*A^[m(q/iX_=sf_8*'Png'@GCd11rPCA-FY\i'u!LEC@8=3Tl]oJd9Le8pBV>=44V"E@OL[Ls6e!.Fb!CbD`g&d)Xq+R,IWQ<PZkS1ERn]97Dg`05'm0?7-dTM`%94YIX>+H1G3D?"X'$1gc)%ZAbUu+Xc@Fd#7UXHs?4oTYRTF?Wd=ut15IX-.1=E%?mnV_OT.>@k.AlBW^Ruau"_TJ#W6D=ueS\eeqKuJL`$A[^Wf(dcE*0sP"-80el$g[Q0%$"@%=JJqEE3u&f2^C*/!g!ljAAqm,c.k11%-:m!0-S6gDo@#`=O.q`R=d0TbJ@rmF'C091eblsLIQ&hd+#ID60DiWOhH\0k3qKFqbkG\;=l2Anh&McXPFD]a]J2lMf.P%7s5,sNO**\jPps_h"$cE\[7m,pZ!_<(M<<kr4+hpouq"%KVJDnd3pgUFu/n6HK'3%SKS%abbG>W"K&a$\VK!#>qe7O3,^WE1RB^0Z,r2GL[6edlA4UH*S`'Sn_)Gc#m#.]'L;@eeC7^j7)f,da^0Dfo#Q0-qW1+>(V&,bk?9:uIC_TSb=bbMH7s5W&,ML\=)jAVpY^#4=V;p#-e)AqiLaW&q-B50q=&?Gf8B6\+6&1jW#iQN_dsB9Y^4MYiXU.lg/^c:7kaPaJG"qL_OJJ$;X[V(P5&jNO/>LUa?UHc,4>OSi@e%tp5]1P8'gr!61/Ra31pp#<$+eG3;UHLCjW2V^:c#I7kkB%X,CE#Q4VZR=RY!/^==L2>XCX^&\VKEaG)UkF$d??UVELr^Y!gYZZ-!erT/Wk(J!p,)UB_4@#1p6=u2*:`5U+T'2t0Xdi>fa@X"n)E]Xkj'&Vg/i@^Tf2"AL1Ok:W4;EJ"+n4t%N<:"qh0"M>'HMJ5!$(^`&LAM,jU5QVBWei=eoV'$W0O]Y&_;K4V`:&"Wb`j)K.\qs4`2<nSOc^3U:&X7b<RU7a],fKq<o]1eJ,^j,H`@!-(Fe/hbMT(:V_*+Pd[fh#hP0q/<W#:a&[=Iu.>%?@-/(l]JY2/gs&lUV3h%F3QL4*?E-m*1l/RuYr,Re%4cB;#.c\cCP%;1LO.)Tp@`2<X/2X0q$m)P]*G71nSnHpf24k%P[cOQX^)u5>r6uJnja.-]7epp^EZKaP?#tXk6KVu3n<.1#!FAM[fAFPhC\b*eL-gV^d[FP-(/^T262(oc1=(Z4?S<OYQC,KT3l`>K3>>VD*Vt"@_5pc36]^%^eX2E3=384SRB89UojXmR;04R,;f?m.n-cp<Yf^lPQ@e[9<IcOHlSFr,C1(Vcoe*)*@C27sBJ#>E/98`sQ=**#:Jlc7`dZ)tI]XCMHW_W0fWT!KMHNh>3PhelO20l]=@j\.6q+GVOAY6d?2j[[NE23j%5OWGR+/m5ja7OpM#EG.-O^J$2"@P(JSJLs\DH2Ig15`"(5ZA`-a#q1#k##R6p^P]?aaQq5K8mQm_`("d>.KQ<s]B9%4RafcN~>endstream
|
||||||
|
endobj
|
||||||
|
77 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2787
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
Gb!#^lYkfA'*"Q<@_NhD('u9a0.T;lE-ARK2V3(](W?CU%,#6/D.H57kWJ9Ps8*6O=dEX@VX/2I#SMpTBP'4Wre?X<3rTuLhrtd3Ad$gX!t5O37kcK'_<;AH7q[=A)$QjJ';oL/(:^M?FORSc3Ln(oNKTW:^h=%=b(mBc1`JYkZT@S9SDUYa//\02.8/,3@.Xmt5>!B>i_Pa`LD%4%&l(tpL+Y]3@D-M\^U)$^-:4jcRlcCZ&4q2EBmu5@jACDT,r)%8Olf.i*XB$NT8IX*j)8``Zmkau"\Z7IcN/C'k*bYt>^(EP^VB$J^OecRlC(;s+r\YfC+;@DTWi7%<XcWI;<.LG`bRr3In^dS7Ef0Y7qisK.sP2"JqZK?@)ec?<9s\GKLPBW^tGWNcY%dXpVYs&85\UO+!V+RdU$,hoEs4BerK:](Y`@0qLZtB7C+6L,1.G=Ng+#*i&F_V7.4"%2M%6eUUrZ3BJIggUlcjKb]ZiZO&1HiE+t]T)*InS/EKYn@TaEYN7XSQAF(kGIhiP<?,K#Uh^jIp*XDu+aWD0IB<OgHIhiI*baJKA@M-piX^`2#l(NNci-\ar^6LW7.<0Im(p3ZcZ"W67=t[(%oON0[K,%2APgrl;T9nfhDL^,7OD&K&r&Pf<[Q`N:WHh4>0n,e,?.KfV$+*=`=:75e;j&\pGQ(QL;I7O:%XUi_NCHp5@@m+H*Ced6^6baDZ,BA2Q_gU]JpEcsW7OaHON)!'P7$\\ZOQR9m5Bg+Ep^*eCEuhnqgB[HoDhuIT8O$I"\Hi:=<rYBY-auVL]7uWoc.`;p*"&fF_?3FFr.[W6P>/_\VQ%4R]eY#'lODjk'kR5NEbI>q"sH;oVcQY#1Pm+5FpaSo<POub>"PI;$b'U6EbjADgea+kUI:kCiJ^=9]0SIJNH20!_<'>JS6M@'$7?tPRK#6E_"::E_oN#QBh>7:7$TBqU`13fl:if\qV_`fM#]-KGF]GOHi[5<e[:j_t`=#&3OP':?s?7$0WRn.mU0Q]/B*K5](/'[%*;H,mQ=YX:oAkEt"h"]o#Kpgq^md7m!.amg/Gj?0B.U$Y+[`!0/&JC9oP0Oo<9?0&n=fK.Gj:pP5AZLZ=m'nFl],'O0]EHhE^sLfGgl(iu"]>VN<Tr94jr1+f'f+;(S"(c'daZWO+rg:jt5ekaOGr&R@"JB'I50%fefDB(a(:f[O`n_ir>[K*\V9Q:4sn]^_)Z$X1WI+K,>gBQ#^(.a1bi,O!D+ksucD\Vt*F@'YVCk2H:15Jp+);E_HFT3DaK!"c*dLL@+VK]7CO>VKRj?TVnFSkhSlkF.%HZ]\O3NI?2h0H&D$JkNLg*pDHFi9-/DF^-6<p\+Y[opKL/9fYCkYO_#EU?#8f(Z[_V\cRJb6d4n,"QYa`m^i1[3r*$rS[N/?Z]nnisGo64B;!,MQe<eZ`]gJ6`hN4h^:^,/>!ZqM^'OH?.(Stm0XdP<=uW;:1-Yq9]0S+e1MUs6g\p*)/9u:X\dd'*F_""fa8HF&-"-c3eO,<1tW8SiO?tNc13:IRJG6(1P)<f"<EV#J+,6f<^A]T>X8tF+_J'N^B3n8H>3e%RWcXi/IC'Q<;Q]3+6MPZ0399@0V@D*R9V;$WSl!-@We4$i9R<PfLlC/r`!8E,Ys%,RLmXrg-#$k@$dj^'laItQ_E.=JX8lf!+,)%5j))ISa+U@o6Kja:d"8ipqV1T$(.nXB6DGMS[TO"ld#6(bh)0.2Ma?QYjo?3bqMUnJ;Suq:b:*=L6Ae$0Lg9;H$Rk:q&pkf`ZCN1W1a(Vp@$;8B)fLhX4^iOfQ$WRcMC![%Z;GE0n7qfGe8#aU'@dpmu$M1i\f2GMnQAX^f#6-d]/=K`=]0Tmbdj&^;giAn#)C*i7R@p\fVKB<)%lA/sorEXVXmk=l72>&:VuThY)GC79"ItIMnMr9SVu%7-_emSQW`%UF?R:6\NBZV_t+[7ogQp*%J@h)oC_Y[*$JE]%6Cb3T4ju^[@t9Fgg")a8(q&N],$;2mf(Mk)OUJhqHMI\j:.K=^.@\l5;uC#eILP$_LpAJ^l$%i$ApB.*bb!kGAe0f=]!qf<5Q^?_ei!A.3@)qf;F>r8Dg<p[qm(WH('fHTctKdGD-,gF3Fip:-^rJ]kjDbZ?eBVj9X(Ak@J@QTG_26B=c6UWjd6^&@I::tf]8mM59;'d7<BQ/uJT'5TV;:Um#>>`i2sRC]P:o`^Z$-%L+`Ne45P!j2XrMk6K7&tD8p)b]6m(eZ[7=K=h"eLU(Qf^8\O6U-otR\p2kT*Got+:t23iZl<AO=rO+cYYn;0m\"7cnTS&+i>dt@Fq^P@\2Nsf\&4+;2SSF0)R,,Tjmp`lWPieXa!B0Kg,k]q3LI%T/N^l""Qf?gMXT$-:AEiGCg3f+qm1PitW*j/&C5rG%t*_9/e@\m:O!SX#f">;]ZdQdq7(H7n\(*>J+]8=kF"RVK.X_DDJ^X?ps%0YiE*Z+o[[0iW;>`6^h@QeFh]`b;2dU$6C'3qrqE5W-%SeY:HG,d7mZh%%[:aT`3f%Jo<e*ZheYpFq.Q:0M!!^V:TX$.)#(A2^88Vi9W2\94%r_)u/3,6d4u0c=1RZX-'B(:<M&S3K'[Clk1mjq5b7Z95]DCU;da<<^P[%4DC=>%'s%_b[p</_=-@ATnM+G>a^+57P^R.LnF5EN4o@YN(<hnCt,a,pgBlRN$Vp1Q75lt><eg"16kTCl?Tc8`<6<pCP'i7F)q`4GSTV[FHNsT9aF(#^HS)]AW>BrY:4V[g_.mF`.42<gbkM@p"J/1(!cd`)c?L&~>endstream
|
||||||
|
endobj
|
||||||
|
78 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 2621
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
Gb!l!lYkNI'*%C7@UPiFV[6_RLE=Fni4*sY,#&bU\_[Rb!_'4:-5;dm=*EAnpUB$2?Qj,]>h[fhJ:lk6b8JT@G@uB2.g'nk)!V(W*oPQgZmu4e!qQj<Q>486/R6RsIQ]E+:<Q\/Z)q4a)N@bQS65P-P4:u9P\$49hh.W'[Wl)i+tsf'o^if8iTu<2r/^p+M509p@d);(0`RB;:-$YYl4WYXW%!.jh<Gp=Y1D[Xa.mHU-p:cGgSqNF,^=G<dkNo-_Mu;n`,tquGWRhmB`K):TS:JBm'Ddn\>L;Z)W/tc;ubk$_J!To\D`mrMs%;eA-C:g(&5l\gmIsXfAd1MJ6NrhRsUhlh"our<7(QtMMhN,>jep0IebpE\>pB`iM:k::[>sO.abA\R$`%$b:FO"1M;N3,G^\c0s4:,)i>--W6<TU28/ZG\p,i9&K.XEG(i?.ajINe1L3.G*?be>>2VkI3[n_HYV<SRZGdZ)3T"buA[mbf+7]X@2:h'ZrQQ=*S4C\#e'Q6*h<84[W%?e_ObPA4B)^M*'>BE7p5+*#o3sZ3d0/^J[O\)R7_;4.-%.$Z?DHc5O#QOb_2>A`iQgVuVB_o`9o%Co=?XXm-](s1$1@$<-,aoO3)s50_rhT9+M_]+aQ3(-SO2[$6nY(G"Nila"n%>odbetT1#9'YmrD1T1H0JQ$J-1(H^$rcR,Nic2aT)C"7Hd+THlIhr!Uqe;r5<r$Pmb$Eb1i2MGk;cg!8)2,qOEJMa_(9)1]#IB>:()OZK'4IO\SbSXr<"\357cRVe:c^W&d6-="Y6SGr%K6dhU3@Tqf)W0Z,-*urMkN_ZZn#0SG5"t#H'""pmo!A4q,#XqA)q"t!'F4)(+A.0Dna/Z(;oeA1<'3'&4kbBl2,F_@X6d0k4YMg/<SuBRM%ObG<@LATC;5F!J/<8Q+U8Hp$B`?:aZ:V6)F7KUIaP/'YT,qK6lScjCQpDc-D%l(hMd6/I+PG>e"\d@(0lsh2F'=AkE&sN0aSgA^[NW*UY/gk(?J(Qs2P[;>/]1K@n&B=_$_lD;((?4<_5T5FMSH#m5N+He2gr4&Rd"ga+qr%BW.t*g;ZuAoYlIfj5XUsI1gEi$'4*.OiX;WHJND'qW<s$er;D!I1lS]P*J#3,?c.$;r5s)Wd[MaAJjU8YmTi&uJZ6Qj8Vji:rQ)>o<2f^"B:io5pD0#Yim4M9F\G8jD*[^ep$38BhKMDShg\@X3""Rj-0WnBHL8!:Q*8'P/d=s:Kn,qMJ7YB?^emD5jBj(gW$(#s6GS'S!Af"TJ>8&ga<Z\RYcSo+[XIAV7CGQ_e$PWWh(EKAl'1hs)Yt9",cQ2hWuXE%^16KSKkf8#WfQeo,:")H^3aqc'@j=Gd3E2J%WTFMAce4#H@r\t^p3i(HJ36PW7+0h[k%m-d9`Z.hV\g-'uDrrMpH$#4=Oi`m_beKUZ?2d)?&=CYg"`[ik9A/^1Z_+(*#Um#<B>A\!r\`43jW8V;&pfm@7U(>F:_dr4jNH@R%_9YL1D<0CD"2E%.SU!nT`V@U'/lb4RHtYI3`),4@26`PNLRjI09,0MD.$f1XN?2,AJ"!d4fak"X;L-6;]I6E\V?d\H0^aq(^H7=17.1916>N)DB#=0W7(S^FmMBdsr(p-e@OW4\O.]^rIgUph;KE'&JJes"51>FeqEDf9H<\sE%jV^AmdM"Jc:C0?\9o!JUKPJt4"AI]N)St:W-kJcDC5:1I]X!VSFY:<Fu6VZUeIS=)jgfU9N9*qd:Qe5;R`u\.e':c3Gee1F51$t5cVh4i:.WAInBC"sh+PE76U31m*o*/65+psZ)0p[sZ'/7VZ\0k4-#PVBQ'U*c.6+Gt[dE_+ef5QgnJWYPnkO7KJh*[<gU6pr*$23ER1MqRqCDO.RARO@GO!1ib04F5q*^bGG>PYE$&ji\=pVr+?%:S_G0t,;aZ]N#(b9O-#M$Z7R5ajG&l,.lYn)rjCG>T69`"I3R[Bbk6N;HsBT5*)?-%%336#j"MYiMF.(:iadQl%iAHk.Fq5%*fi/cWtG`m-d;-tk1-g-Ob+g.o*:cdIJir0`a1nY_)"'B`f5=^eO6K1riWW];7L_a)<8:H.dp=\k-sb[/L]Cnm`,#;[6lWq<^6_T0LVAn(B>DEkl'Oo,05ndO=l%.ho4,&ppJ>kCs!r/!EjZ/0(SJ1aoF=nE/<ZKq6[8sN7tDA?P7c!$>pq^emVq9Dc(LUA7X,`m[sPjB5O>-c)+q_0,2A_D,$Ria3mOiZQ\r^j)gq0+"Y=3+?+'OUrm(Eh3KECT6MUW&YRNHWFlNI30^UpSX!H(?F8oN&I"Zt*"&o[&6WL3K%/B5'kjhlg:W_qiW$2"2(n^*rE;T6eQ+im5d?Utp8`U[dXIU1Y[)6iYC",hYa.rWpRuYeEBu4k<8Wh1sR&G:UFSL'-`,oIpVK94./nkM>J]X1f%;R)pYP_16]`Oob</\"hemYnru7AHf07!^n##J_@5cm:`pX8ALrL$&8ZjYtp(*UOpj<Q1I4a#?,m$$&aWt<lY[nSZ%mBA(Ad@"s;#pjkh'\:A7ki3$!ArS%c%BB_E_cnJ/S5_bWTgV,)_<^8>*QdMQg9?g<PSmd2&4>oXRqSB?R9WM)r,s);biaaX;W,@&%~>endstream
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 79
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000061 00000 n
|
||||||
|
0000000160 00000 n
|
||||||
|
0000000267 00000 n
|
||||||
|
0000000379 00000 n
|
||||||
|
0000000526 00000 n
|
||||||
|
0000000674 00000 n
|
||||||
|
0000000822 00000 n
|
||||||
|
0000000969 00000 n
|
||||||
|
0000001116 00000 n
|
||||||
|
0000001264 00000 n
|
||||||
|
0000001413 00000 n
|
||||||
|
0000001562 00000 n
|
||||||
|
0000001710 00000 n
|
||||||
|
0000001859 00000 n
|
||||||
|
0000002007 00000 n
|
||||||
|
0000002156 00000 n
|
||||||
|
0000002304 00000 n
|
||||||
|
0000002453 00000 n
|
||||||
|
0000002601 00000 n
|
||||||
|
0000002749 00000 n
|
||||||
|
0000002897 00000 n
|
||||||
|
0000003045 00000 n
|
||||||
|
0000003194 00000 n
|
||||||
|
0000003342 00000 n
|
||||||
|
0000003491 00000 n
|
||||||
|
0000003640 00000 n
|
||||||
|
0000003789 00000 n
|
||||||
|
0000003938 00000 n
|
||||||
|
0000004086 00000 n
|
||||||
|
0000004234 00000 n
|
||||||
|
0000004382 00000 n
|
||||||
|
0000004530 00000 n
|
||||||
|
0000004646 00000 n
|
||||||
|
0000005060 00000 n
|
||||||
|
0000005166 00000 n
|
||||||
|
0000005286 00000 n
|
||||||
|
0000005370 00000 n
|
||||||
|
0000005448 00000 n
|
||||||
|
0000005654 00000 n
|
||||||
|
0000005860 00000 n
|
||||||
|
0000006066 00000 n
|
||||||
|
0000006272 00000 n
|
||||||
|
0000006359 00000 n
|
||||||
|
0000006676 00000 n
|
||||||
|
0000006751 00000 n
|
||||||
|
0000006852 00000 n
|
||||||
|
0000006958 00000 n
|
||||||
|
0000007065 00000 n
|
||||||
|
0000007223 00000 n
|
||||||
|
0000007322 00000 n
|
||||||
|
0000007432 00000 n
|
||||||
|
0000007537 00000 n
|
||||||
|
0000007678 00000 n
|
||||||
|
0000007777 00000 n
|
||||||
|
0000007885 00000 n
|
||||||
|
0000008001 00000 n
|
||||||
|
0000008141 00000 n
|
||||||
|
0000008240 00000 n
|
||||||
|
0000008352 00000 n
|
||||||
|
0000008499 00000 n
|
||||||
|
0000008585 00000 n
|
||||||
|
0000008740 00000 n
|
||||||
|
0000008826 00000 n
|
||||||
|
0000008971 00000 n
|
||||||
|
0000009057 00000 n
|
||||||
|
0000009165 00000 n
|
||||||
|
0000009310 00000 n
|
||||||
|
0000009398 00000 n
|
||||||
|
0000009500 00000 n
|
||||||
|
0000009615 00000 n
|
||||||
|
0000009733 00000 n
|
||||||
|
0000009845 00000 n
|
||||||
|
0000009945 00000 n
|
||||||
|
0000010034 00000 n
|
||||||
|
0000011131 00000 n
|
||||||
|
0000014699 00000 n
|
||||||
|
0000017665 00000 n
|
||||||
|
0000020544 00000 n
|
||||||
|
trailer
|
||||||
|
<<
|
||||||
|
/ID
|
||||||
|
[<58c8102b70531cef7ffe0ad93695a99d><58c8102b70531cef7ffe0ad93695a99d>]
|
||||||
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
|
/Info 43 0 R
|
||||||
|
/Root 42 0 R
|
||||||
|
/Size 79
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
23257
|
||||||
|
%%EOF
|
||||||
@@ -47,6 +47,7 @@ Click a link to open a guide. Section links jump within the document (GitHub, Cu
|
|||||||
| [20260608_STREAM_DUMP_DEBUG.md](20260608_STREAM_DUMP_DEBUG.md) | FR 0.1 stream dump spec. |
|
| [20260608_STREAM_DUMP_DEBUG.md](20260608_STREAM_DUMP_DEBUG.md) | FR 0.1 stream dump spec. |
|
||||||
| [OPEN_TASKS_GRAPH.md](OPEN_TASKS_GRAPH.md) | Open tasks dependency graph (Mermaid). |
|
| [OPEN_TASKS_GRAPH.md](OPEN_TASKS_GRAPH.md) | Open tasks dependency graph (Mermaid). |
|
||||||
| [20260608_BE_SERVICES_and_infra.md](20260608_BE_SERVICES_and_infra.md) | BE service map, validation status, SFU preview (gray). |
|
| [20260608_BE_SERVICES_and_infra.md](20260608_BE_SERVICES_and_infra.md) | BE service map, validation status, SFU preview (gray). |
|
||||||
|
| [20260610_SERVICES.md](20260610_SERVICES.md) | Exposed services catalog — web UI, APIs, curl, validation (2026-06-10). |
|
||||||
| [USB_HDMI_CAST.md](USB_HDMI_CAST.md) | Roadmap D/E: HDMI awareness, USB-tether transport. |
|
| [USB_HDMI_CAST.md](USB_HDMI_CAST.md) | Roadmap D/E: HDMI awareness, USB-tether transport. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ SUBTITLE = {
|
|||||||
"REMOTE_ACCESS_IMPL.md": "WireGuard v1 — implementation and deploy",
|
"REMOTE_ACCESS_IMPL.md": "WireGuard v1 — implementation and deploy",
|
||||||
"REMOTE_ACCESS_VALIDATION.md": "Linux CLI validation — device sim + E2E",
|
"REMOTE_ACCESS_VALIDATION.md": "Linux CLI validation — device sim + E2E",
|
||||||
"INFRA.md": "Production topology — FE, BE, ports",
|
"INFRA.md": "Production topology — FE, BE, ports",
|
||||||
|
"20260610_SERVICES.md": "Exposed services catalog — URLs, APIs, validation — 2026-06-10",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -523,6 +523,13 @@ body { --footer-h: 40px; }
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
padding: 3rem 16px calc(var(--footer-h, 40px) + 1.5rem);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.login-page .login-card {
|
||||||
|
position: relative;
|
||||||
|
z-index: 6;
|
||||||
}
|
}
|
||||||
.login-card {
|
.login-card {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
|
|||||||
@@ -162,6 +162,10 @@ if ($route === '/login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($route === '/login') {
|
if ($route === '/login') {
|
||||||
|
if (Auth::pending2faUserId() > 0) {
|
||||||
|
header('Location: ' . $base . '/two-factor');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
require __DIR__ . '/../views/login.php';
|
require __DIR__ . '/../views/login.php';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,14 +101,15 @@ final class Auth {
|
|||||||
public static function completeTotpLogin(string $code): bool {
|
public static function completeTotpLogin(string $code): bool {
|
||||||
$uid = (int) ($_SESSION['pending_2fa_user_id'] ?? 0);
|
$uid = (int) ($_SESSION['pending_2fa_user_id'] ?? 0);
|
||||||
$exp = (int) ($_SESSION['pending_2fa_exp'] ?? 0);
|
$exp = (int) ($_SESSION['pending_2fa_exp'] ?? 0);
|
||||||
unset($_SESSION['pending_2fa_user_id'], $_SESSION['pending_2fa_exp']);
|
|
||||||
if ($uid <= 0 || $exp < time()) {
|
if ($uid <= 0 || $exp < time()) {
|
||||||
|
self::clearPending2fa();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$secret = AuthFactors::getTotpSecret($uid);
|
$secret = AuthFactors::getTotpSecret($uid);
|
||||||
if ($secret === null || !AuthTotp::verify($secret, $code)) {
|
if ($secret === null || !AuthTotp::verify($secret, $code)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
self::clearPending2fa();
|
||||||
$stmt = Database::pdo()->prepare('SELECT * FROM users WHERE id = ? LIMIT 1');
|
$stmt = Database::pdo()->prepare('SELECT * FROM users WHERE id = ? LIMIT 1');
|
||||||
$stmt->execute([$uid]);
|
$stmt->execute([$uid]);
|
||||||
$row = $stmt->fetch();
|
$row = $stmt->fetch();
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ $bp = Auth::basePath();
|
|||||||
<script src="<?= h($bp) ?>/assets/js/i18n.js" defer></script>
|
<script src="<?= h($bp) ?>/assets/js/i18n.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="login-page" data-base-path="<?= h($bp) ?>">
|
<body class="login-page" data-base-path="<?= h($bp) ?>">
|
||||||
|
<div class="login-locale">
|
||||||
|
<label class="toolbar-select locale-toolbar-select" data-i18n-title="lang.label" title="Language">
|
||||||
|
<span class="locale-flag" aria-hidden="true">🇬🇧</span>
|
||||||
|
<select class="lang-select" data-i18n-aria="lang.label" aria-label="Language">
|
||||||
|
<option value="en">EN</option>
|
||||||
|
<option value="ru">RU</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<form class="login-card" method="post" action="<?= h($bp) ?>/two-factor">
|
<form class="login-card" method="post" action="<?= h($bp) ?>/two-factor">
|
||||||
<h1 data-i18n="twofa.title">Two-factor authentication</h1>
|
<h1 data-i18n="twofa.title">Two-factor authentication</h1>
|
||||||
<p class="muted" data-i18n="twofa.hint">Enter the 6-digit code from your authenticator app.</p>
|
<p class="muted" data-i18n="twofa.hint">Enter the 6-digit code from your authenticator app.</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user