mirror of
git://f0xx.org/android_cast
synced 2026-07-29 02:59:00 +03:00
sync be
This commit is contained in:
@@ -3,8 +3,10 @@ package com.foxx.androidcast.network;
|
|||||||
/** Canonical production BE URLs on {@code apps.f0xx.org} (network self-test + defaults). */
|
/** Canonical production BE URLs on {@code apps.f0xx.org} (network self-test + defaults). */
|
||||||
public final class BackendEndpoints {
|
public final class BackendEndpoints {
|
||||||
public static final String APPS_HOST = "apps.f0xx.org";
|
public static final String APPS_HOST = "apps.f0xx.org";
|
||||||
public static final String CRASHES_BASE =
|
public static final String ISSUES_BASE =
|
||||||
"https://" + APPS_HOST + "/app/androidcast_project/crashes";
|
"https://" + APPS_HOST + "/app/androidcast_project/issues";
|
||||||
|
/** @deprecated use {@link #ISSUES_BASE}; nginx 301 redirects legacy path. */
|
||||||
|
public static final String CRASHES_BASE = ISSUES_BASE;
|
||||||
public static final String GRAPHS_BASE =
|
public static final String GRAPHS_BASE =
|
||||||
"https://" + APPS_HOST + "/app/androidcast_project/graphs";
|
"https://" + APPS_HOST + "/app/androidcast_project/graphs";
|
||||||
public static final String HEARTBEAT_URL = CRASHES_BASE + "/api/heartbeat.php";
|
public static final String HEARTBEAT_URL = CRASHES_BASE + "/api/heartbeat.php";
|
||||||
@@ -27,9 +29,15 @@ public final class BackendEndpoints {
|
|||||||
return trimmed;
|
return trimmed;
|
||||||
}
|
}
|
||||||
if (trimmed.contains("://f0xx.org/app/androidcast_project/crashes")
|
if (trimmed.contains("://f0xx.org/app/androidcast_project/crashes")
|
||||||
|| trimmed.contains("://foxx.org/app/androidcast_project/crashes")) {
|
|| trimmed.contains("://foxx.org/app/androidcast_project/crashes")
|
||||||
|
|| trimmed.contains("://f0xx.org/app/androidcast_project/issues")
|
||||||
|
|| trimmed.contains("://foxx.org/app/androidcast_project/issues")) {
|
||||||
return trimmed.replace("://f0xx.org/", "://" + APPS_HOST + "/")
|
return trimmed.replace("://f0xx.org/", "://" + APPS_HOST + "/")
|
||||||
.replace("://foxx.org/", "://" + APPS_HOST + "/");
|
.replace("://foxx.org/", "://" + APPS_HOST + "/")
|
||||||
|
.replace("/crashes/", "/issues/");
|
||||||
|
}
|
||||||
|
if (trimmed.contains("/crashes/")) {
|
||||||
|
return trimmed.replace("/crashes/", "/issues/");
|
||||||
}
|
}
|
||||||
return trimmed;
|
return trimmed;
|
||||||
}
|
}
|
||||||
@@ -40,8 +48,9 @@ public final class BackendEndpoints {
|
|||||||
return GRAPH_UPLOAD_URL;
|
return GRAPH_UPLOAD_URL;
|
||||||
}
|
}
|
||||||
String trimmed = normalizeCrashesUrl(crashesUrl.trim());
|
String trimmed = normalizeCrashesUrl(crashesUrl.trim());
|
||||||
if (trimmed.contains("/crashes/")) {
|
if (trimmed.contains("/issues/")) {
|
||||||
return trimmed.replace("/crashes/", "/graphs/").replace("/api/upload.php", "/api/graph_upload.php");
|
return trimmed.replace("/issues/", "/graphs/")
|
||||||
|
.replace("/api/upload.php", "/api/graph_upload.php");
|
||||||
}
|
}
|
||||||
return GRAPH_UPLOAD_URL;
|
return GRAPH_UPLOAD_URL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public class BackendEndpointsTest {
|
|||||||
@Test
|
@Test
|
||||||
public void canonicalUrlsUseAppsHost() {
|
public void canonicalUrlsUseAppsHost() {
|
||||||
assertTrue(BackendEndpoints.HEARTBEAT_URL.startsWith("https://apps.f0xx.org/"));
|
assertTrue(BackendEndpoints.HEARTBEAT_URL.startsWith("https://apps.f0xx.org/"));
|
||||||
assertTrue(BackendEndpoints.UPLOAD_URL.contains("/api/upload.php"));
|
assertTrue(BackendEndpoints.UPLOAD_URL.contains("/issues/api/upload.php"));
|
||||||
assertTrue(BackendEndpoints.ENTITLEMENTS_FETCH_URL.contains("/entitlements/api/fetch.php"));
|
assertTrue(BackendEndpoints.ENTITLEMENTS_FETCH_URL.contains("/entitlements/api/fetch.php"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ if (!is_file($__platformDir . '/footer.php')) {
|
|||||||
}
|
}
|
||||||
require_once $__platformDir . '/footer.php';
|
require_once $__platformDir . '/footer.php';
|
||||||
$__sessionFile = $__platformDir . '/shared_session.php';
|
$__sessionFile = $__platformDir . '/shared_session.php';
|
||||||
$hubCrashesBase = '/app/androidcast_project/crashes';
|
$hubProjectBase = '/app/androidcast_project';
|
||||||
|
$hubIssuesBase = '/app/androidcast_project/issues';
|
||||||
$hubUsername = '';
|
$hubUsername = '';
|
||||||
if (is_file($__sessionFile) && function_exists('session_start')) {
|
if (is_file($__sessionFile) && function_exists('session_start')) {
|
||||||
require_once $__sessionFile;
|
require_once $__sessionFile;
|
||||||
@@ -35,7 +36,7 @@ function hub_h(string $s): string {
|
|||||||
if (l === 'en' || l === 'ru') document.documentElement.setAttribute('lang', l);
|
if (l === 'en' || l === 'ru') document.documentElement.setAttribute('lang', l);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="/app/androidcast_project/crashes/assets/css/app.css">
|
<link rel="stylesheet" href="/app/androidcast_project/issues/assets/css/app.css">
|
||||||
<link rel="stylesheet" href="hub.css?v=20260610overlay">
|
<link rel="stylesheet" href="hub.css?v=20260610overlay">
|
||||||
<link rel="stylesheet" href="hub-logo-layers.css?v=20260610overlay">
|
<link rel="stylesheet" href="hub-logo-layers.css?v=20260610overlay">
|
||||||
<link rel="stylesheet" href="hub-landing.css?v=20260613layout">
|
<link rel="stylesheet" href="hub-landing.css?v=20260613layout">
|
||||||
@@ -46,12 +47,12 @@ function hub_h(string $s): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script src="/app/androidcast_project/crashes/assets/js/nav_shell.js" defer></script>
|
<script src="/app/androidcast_project/issues/assets/js/nav_shell.js" defer></script>
|
||||||
<script src="assets/hub-i18n.js" defer></script>
|
<script src="assets/hub-i18n.js" defer></script>
|
||||||
<script src="assets/hub-landing.js" defer></script>
|
<script src="assets/hub-landing.js" defer></script>
|
||||||
<script src="/app/androidcast_project/crashes/assets/js/cookie_consent.js" defer></script>
|
<script src="/app/androidcast_project/issues/assets/js/cookie_consent.js" defer></script>
|
||||||
<script src="assets/analytics.config.js"></script>
|
<script src="assets/analytics.config.js"></script>
|
||||||
<script src="/app/androidcast_project/crashes/assets/js/analytics.js" defer></script>
|
<script src="/app/androidcast_project/issues/assets/js/analytics.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="hub-page hub-landing">
|
<body class="hub-page hub-landing">
|
||||||
<div id="lcd-alphabet-host" hidden aria-hidden="true"></div>
|
<div id="lcd-alphabet-host" hidden aria-hidden="true"></div>
|
||||||
@@ -139,15 +140,15 @@ function hub_h(string $s): string {
|
|||||||
<span class="nav-icon nav-icon--menu" aria-hidden="true"></span>
|
<span class="nav-icon nav-icon--menu" aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="nav-list">
|
<ul class="nav-list">
|
||||||
<li><a href="crashes/?view=home" class="nav-link" data-i18n-title="nav.console" title="Console"><span class="nav-icon nav-icon--home"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.console">Console</span><span class="nav-desc" data-i18n="nav.console_desc">Crash triage home and workspace cards.</span></span></a></li>
|
<li><a href="issues/?view=home" class="nav-link" data-i18n-title="nav.console" title="Console"><span class="nav-icon nav-icon--home"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.console">Console</span><span class="nav-desc" data-i18n="nav.console_desc">Crash triage home and workspace cards.</span></span></a></li>
|
||||||
<li><a href="git/" class="nav-link" data-i18n-title="nav.git" title="Git"><span class="nav-icon nav-icon--git"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.git">Git</span><span class="nav-desc" data-i18n="nav.git_desc">Browse repos, branches, and tags.</span></span></a></li>
|
<li><a href="git/" class="nav-link" data-i18n-title="nav.git" title="Git"><span class="nav-icon nav-icon--git"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.git">Git</span><span class="nav-desc" data-i18n="nav.git_desc">Browse repos, branches, and tags.</span></span></a></li>
|
||||||
<li><a href="crashes/?view=reports" class="nav-link" data-i18n-title="nav.issues" title="Issues"><span class="nav-icon nav-icon--reports"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.issues">Issues</span><span class="nav-desc" data-i18n="nav.issues_desc">Browse and triage crash reports.</span></span></a></li>
|
<li><a href="issues/?view=reports" class="nav-link" data-i18n-title="nav.issues" title="Issues"><span class="nav-icon nav-icon--reports"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.issues">Issues</span><span class="nav-desc" data-i18n="nav.issues_desc">Browse and triage crash reports.</span></span></a></li>
|
||||||
<li><a href="crashes/?view=tickets" class="nav-link" data-i18n-title="nav.tickets" title="Tickets"><span class="nav-icon nav-icon--tickets"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.tickets">Tickets</span><span class="nav-desc" data-i18n="nav.tickets_desc">Roadmap tasks, QA items, and tags.</span></span></a></li>
|
<li><a href="issues/?view=tickets" class="nav-link" data-i18n-title="nav.tickets" title="Tickets"><span class="nav-icon nav-icon--tickets"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.tickets">Tickets</span><span class="nav-desc" data-i18n="nav.tickets_desc">Roadmap tasks, QA items, and tags.</span></span></a></li>
|
||||||
<li><a href="graphs/" class="nav-link" data-i18n-title="nav.graphs" title="Analytics"><span class="nav-icon nav-icon--graphs"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.graphs">Analytics</span><span class="nav-desc" data-i18n="nav.graphs_desc">Sessions, live cast stats, and device activity.</span></span></a></li>
|
<li><a href="graphs/" class="nav-link" data-i18n-title="nav.graphs" title="Analytics"><span class="nav-icon nav-icon--graphs"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.graphs">Analytics</span><span class="nav-desc" data-i18n="nav.graphs_desc">Sessions, live cast stats, and device activity.</span></span></a></li>
|
||||||
<li><a href="crashes/?view=live_sessions" class="nav-link" data-i18n-title="nav.live_sessions" title="Live sessions"><span class="nav-icon nav-icon--live"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.live_sessions">Live sessions</span><span class="nav-desc" data-i18n="nav.live_sessions_desc">Cast intents, join stats, session history.</span></span></a></li>
|
<li><a href="issues/?view=live_sessions" class="nav-link" data-i18n-title="nav.live_sessions" title="Live sessions"><span class="nav-icon nav-icon--live"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.live_sessions">Live sessions</span><span class="nav-desc" data-i18n="nav.live_sessions_desc">Cast intents, join stats, session history.</span></span></a></li>
|
||||||
<li><a href="crashes/live/education" class="nav-link" data-i18n-title="nav.education" title="Education demo"><span class="nav-icon nav-icon--education"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.education">Education (demo)</span><span class="nav-desc" data-i18n="nav.education_desc">Browser screen-share trial (5 min).</span></span></a></li>
|
<li><a href="issues/live/education" class="nav-link" data-i18n-title="nav.education" title="Education demo"><span class="nav-icon nav-icon--education"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.education">Education (demo)</span><span class="nav-desc" data-i18n="nav.education_desc">Browser screen-share trial (5 min).</span></span></a></li>
|
||||||
<li><a href="crashes/?view=remote_access" class="nav-link" data-i18n-title="nav.remote" title="Remote access"><span class="nav-icon nav-icon--remote"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.remote">Remote access</span><span class="nav-desc" data-i18n="nav.remote_desc">WireGuard sessions and device reachability.</span></span></a></li>
|
<li><a href="issues/?view=remote_access" class="nav-link" data-i18n-title="nav.remote" title="Remote access"><span class="nav-icon nav-icon--remote"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.remote">Remote access</span><span class="nav-desc" data-i18n="nav.remote_desc">WireGuard sessions and device reachability.</span></span></a></li>
|
||||||
<li><a href="crashes/?view=short_links" class="nav-link" data-i18n-title="nav.short_links" title="Short links"><span class="nav-icon nav-icon--link"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.short_links">Short links</span><span class="nav-desc" data-i18n="nav.short_links_desc">Mint bearer tokens and shorten URLs for s.f0xx.org.</span></span></a></li>
|
<li><a href="issues/?view=short_links" class="nav-link" data-i18n-title="nav.short_links" title="Short links"><span class="nav-icon nav-icon--link"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.short_links">Short links</span><span class="nav-desc" data-i18n="nav.short_links_desc">Mint bearer tokens and shorten URLs for s.f0xx.org.</span></span></a></li>
|
||||||
<li><a href="build/" class="nav-link" data-i18n-title="nav.builder" title="Builder"><span class="nav-icon nav-icon--builder"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.builder">Builder</span><span class="nav-desc" data-i18n="nav.builder_desc">Docker CI for APK baking and OTA artifacts.</span></span></a></li>
|
<li><a href="build/" class="nav-link" data-i18n-title="nav.builder" title="Builder"><span class="nav-icon nav-icon--builder"></span><span class="nav-text"><span class="nav-label" data-i18n="nav.builder">Builder</span><span class="nav-desc" data-i18n="nav.builder_desc">Docker CI for APK baking and OTA artifacts.</span></span></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="nav-user">
|
<div class="nav-user">
|
||||||
@@ -156,14 +157,14 @@ function hub_h(string $s): string {
|
|||||||
<span class="nav-icon nav-icon--user" aria-hidden="true"></span>
|
<span class="nav-icon nav-icon--user" aria-hidden="true"></span>
|
||||||
<span class="nav-label"><?= hub_h($hubUsername) ?></span>
|
<span class="nav-label"><?= hub_h($hubUsername) ?></span>
|
||||||
</span>
|
</span>
|
||||||
<a href="<?= hub_h($hubCrashesBase) ?>/account-security"
|
<a href="<?= hub_h($hubIssuesBase) ?>/account-security"
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
data-i18n-aria="nav.security" data-i18n-title="nav.security"
|
data-i18n-aria="nav.security" data-i18n-title="nav.security"
|
||||||
aria-label="Security" title="Security">
|
aria-label="Security" title="Security">
|
||||||
<span class="nav-icon nav-icon--user" aria-hidden="true"></span>
|
<span class="nav-icon nav-icon--user" aria-hidden="true"></span>
|
||||||
<span class="nav-label" data-i18n="nav.security">Security</span>
|
<span class="nav-label" data-i18n="nav.security">Security</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?= hub_h($hubCrashesBase) ?>/logout"
|
<a href="<?= hub_h($hubProjectBase) ?>/logout"
|
||||||
class="nav-link nav-link--logout"
|
class="nav-link nav-link--logout"
|
||||||
data-i18n-aria="nav.logout" data-i18n-title="nav.logout"
|
data-i18n-aria="nav.logout" data-i18n-title="nav.logout"
|
||||||
aria-label="Logout" title="Logout">
|
aria-label="Logout" title="Logout">
|
||||||
@@ -171,7 +172,7 @@ function hub_h(string $s): string {
|
|||||||
<span class="nav-label" data-i18n="nav.logout">Logout</span>
|
<span class="nav-label" data-i18n="nav.logout">Logout</span>
|
||||||
</a>
|
</a>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<a href="<?= hub_h($hubCrashesBase) ?>/login"
|
<a href="<?= hub_h($hubProjectBase) ?>/login"
|
||||||
class="nav-link"
|
class="nav-link"
|
||||||
data-i18n-aria="nav.sign_in" data-i18n-title="nav.sign_in"
|
data-i18n-aria="nav.sign_in" data-i18n-title="nav.sign_in"
|
||||||
aria-label="Sign in" title="Sign in">
|
aria-label="Sign in" title="Sign in">
|
||||||
@@ -414,7 +415,7 @@ function hub_h(string $s): string {
|
|||||||
var docsToc = document.getElementById('hub-docs-toc');
|
var docsToc = document.getElementById('hub-docs-toc');
|
||||||
var docsPanels = document.getElementById('hub-docs-panels');
|
var docsPanels = document.getElementById('hub-docs-panels');
|
||||||
var compassLayer = document.getElementById('hub-compass-layer');
|
var compassLayer = document.getElementById('hub-compass-layer');
|
||||||
var docsApi = '/app/androidcast_project/crashes/api/hub_deploy_docs.php';
|
var docsApi = '/app/androidcast_project/issues/api/hub_deploy_docs.php';
|
||||||
var docsQueue = [];
|
var docsQueue = [];
|
||||||
var docsLoading = false;
|
var docsLoading = false;
|
||||||
var docsCache = {};
|
var docsCache = {};
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<div class="landing-page-inner card card--lift">
|
<div class="landing-page-inner card card--lift">
|
||||||
<h2 data-i18n="page6.title">Resources</h2>
|
<h2 data-i18n="page6.title">Resources</h2>
|
||||||
<p class="landing-lead" data-i18n="page6.lead">Documentation, deployment guides, and integration patterns for your environment.</p>
|
<p class="landing-lead" data-i18n="page6.lead">Documentation, deployment guides, and integration patterns for your environment.</p>
|
||||||
<p><a class="btn" href="crashes/">Crashes console</a> <a class="btn" href="build/">Builder</a> <a class="btn" href="git/">Git browser</a></p>
|
<p><a class="btn" href="issues/">Issues console</a> <a class="btn" href="build/">Builder</a> <a class="btn" href="git/">Git browser</a></p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,12 @@
|
|||||||
* Digest: SHA256 fcd4a89ef4201736923d0e7e1ad88868bba7e39eadb9fcf0ffb9d1ba7efecd12
|
* Digest: SHA256 fcd4a89ef4201736923d0e7e1ad88868bba7e39eadb9fcf0ffb9d1ba7efecd12
|
||||||
*/
|
*/
|
||||||
return [
|
return [
|
||||||
'app_name' => 'Android Cast Issues',
|
'app_name' => 'Android Cast',
|
||||||
// Production: '/app/androidcast_project/crashes' — Local php -S: use '' (empty string)
|
// Console (issues/tickets/…) — production path
|
||||||
'base_path' => '/app/androidcast_project/crashes',
|
'project_base_path' => '/app/androidcast_project',
|
||||||
|
'base_path' => '/app/androidcast_project/issues',
|
||||||
|
// Legacy alias kept for redirects/nginx; do not use in new links
|
||||||
|
'legacy_base_path' => '/app/androidcast_project/crashes',
|
||||||
'db' => [
|
'db' => [
|
||||||
// sqlite = default file DB; mysql = MariaDB/MySQL (recommended on BE for concurrency)
|
// sqlite = default file DB; mysql = MariaDB/MySQL (recommended on BE for concurrency)
|
||||||
'driver' => 'sqlite', // sqlite | mysql | mariadb (alias)
|
'driver' => 'sqlite', // sqlite | mysql | mariadb (alias)
|
||||||
|
|||||||
@@ -11,8 +11,9 @@
|
|||||||
* Digest: SHA256 8b7149a4cd8a0f7d13ee8a22535c3697e919e35b9fe5bb8a80764769cc6c6853
|
* Digest: SHA256 8b7149a4cd8a0f7d13ee8a22535c3697e919e35b9fe5bb8a80764769cc6c6853
|
||||||
*/
|
*/
|
||||||
return [
|
return [
|
||||||
'app_name' => 'Android Cast Crashes',
|
'app_name' => 'Android Cast',
|
||||||
'base_path' => '/app/androidcast_project/crashes',
|
'project_base_path' => '/app/androidcast_project',
|
||||||
|
'base_path' => '/app/androidcast_project/issues',
|
||||||
'db' => [
|
'db' => [
|
||||||
'driver' => 'mysql', // sqlite | mysql
|
'driver' => 'mysql', // sqlite | mysql
|
||||||
'sqlite_path' => __DIR__ . '/../data/crashes.sqlite',
|
'sqlite_path' => __DIR__ . '/../data/crashes.sqlite',
|
||||||
|
|||||||
@@ -33,40 +33,56 @@ server {
|
|||||||
try_files $uri $uri/ =404;
|
try_files $uri $uri/ =404;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location = /app/androidcast_project/issues {
|
||||||
|
return 301 /app/androidcast_project/issues/;
|
||||||
|
}
|
||||||
|
|
||||||
location = /app/androidcast_project/crashes {
|
location = /app/androidcast_project/crashes {
|
||||||
return 301 /app/androidcast_project/crashes/;
|
return 301 /app/androidcast_project/issues/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/app/androidcast_project/crashes(/.*)?$ {
|
||||||
|
return 301 /app/androidcast_project/issues$1;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/app/androidcast_project/(login|logout|register|two-factor|verify-email)(/|$) {
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_pass unix:/run/php-fpm.socket;
|
||||||
|
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
||||||
|
fastcgi_param SCRIPT_NAME /app/androidcast_project/issues/index.php;
|
||||||
|
fastcgi_param REQUEST_URI $request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /app/androidcast_project/issues/assets/ {
|
||||||
|
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /app/androidcast_project/issues/api/upload.php {
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_pass unix:/run/php-fpm.socket;
|
||||||
|
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/upload.php;
|
||||||
|
fastcgi_param SCRIPT_NAME /app/androidcast_project/issues/api/upload.php;
|
||||||
|
fastcgi_param REQUEST_URI $request_uri;
|
||||||
|
client_max_body_size 4m;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ^~ /app/androidcast_project/issues/ {
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_pass unix:/run/php-fpm.socket;
|
||||||
|
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
||||||
|
fastcgi_param SCRIPT_NAME /app/androidcast_project/issues/index.php;
|
||||||
|
fastcgi_param REQUEST_URI $request_uri;
|
||||||
|
client_max_body_size 4m;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /app/androidcast_project/graphs {
|
location = /app/androidcast_project/graphs {
|
||||||
return 301 /app/androidcast_project/graphs/;
|
return 301 /app/androidcast_project/graphs/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location ^~ /app/androidcast_project/crashes/assets/ {
|
|
||||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ^~ /app/androidcast_project/graphs/assets/ {
|
location ^~ /app/androidcast_project/graphs/assets/ {
|
||||||
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
|
alias /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/assets/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location = /app/androidcast_project/crashes/api/upload.php {
|
|
||||||
include fastcgi_params;
|
|
||||||
fastcgi_pass unix:/run/php-fpm.socket;
|
|
||||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/api/upload.php;
|
|
||||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/api/upload.php;
|
|
||||||
fastcgi_param REQUEST_URI $request_uri;
|
|
||||||
client_max_body_size 4m;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ^~ /app/androidcast_project/crashes/ {
|
|
||||||
include fastcgi_params;
|
|
||||||
fastcgi_pass unix:/run/php-fpm.socket;
|
|
||||||
fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/apps/app/androidcast_project/android_cast/examples/crash_reporter/backend/public/index.php;
|
|
||||||
fastcgi_param SCRIPT_NAME /app/androidcast_project/crashes/index.php;
|
|
||||||
fastcgi_param REQUEST_URI $request_uri;
|
|
||||||
client_max_body_size 4m;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /app/androidcast_project/graphs/api/graphs.php {
|
location = /app/androidcast_project/graphs/api/graphs.php {
|
||||||
include fastcgi_params;
|
include fastcgi_params;
|
||||||
fastcgi_pass unix:/run/php-fpm.socket;
|
fastcgi_pass unix:/run/php-fpm.socket;
|
||||||
|
|||||||
@@ -2317,6 +2317,14 @@ button.report-tag--filter:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Nav icons: live + education */
|
/* Nav icons: live + education */
|
||||||
|
.nav-icon--live {
|
||||||
|
width: 18px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
.nav-icon--education {
|
||||||
|
width: 18px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
.nav-icon--live::before {
|
.nav-icon--live::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -2361,3 +2369,17 @@ button.report-tag--filter:hover {
|
|||||||
background: rgba(52, 211, 153, 0.15);
|
background: rgba(52, 211, 153, 0.15);
|
||||||
color: #6ee7b7;
|
color: #6ee7b7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.twofa-qr-wrap {
|
||||||
|
margin: 12px 0 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.twofa-qr {
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
.twofa-qr-wrap figcaption {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
(function () {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function boot() {
|
||||||
|
var form = document.querySelector('form.login-card');
|
||||||
|
var input = document.getElementById('twofa-code-input');
|
||||||
|
if (!form || !input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
input.focus();
|
||||||
|
input.select();
|
||||||
|
|
||||||
|
function digitsOnly(val) {
|
||||||
|
return String(val || '').replace(/\D/g, '').slice(0, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeSubmit() {
|
||||||
|
if (input.value.length === 6) {
|
||||||
|
if (typeof form.requestSubmit === 'function') {
|
||||||
|
form.requestSubmit();
|
||||||
|
} else {
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.addEventListener('input', function () {
|
||||||
|
var next = digitsOnly(input.value);
|
||||||
|
if (next !== input.value) {
|
||||||
|
input.value = next;
|
||||||
|
}
|
||||||
|
maybeSubmit();
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('paste', function (ev) {
|
||||||
|
ev.preventDefault();
|
||||||
|
var text = (ev.clipboardData && ev.clipboardData.getData('text')) || '';
|
||||||
|
input.value = digitsOnly(text);
|
||||||
|
maybeSubmit();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function (ev) {
|
||||||
|
if (!/^[0-9]$/.test(ev.key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var ae = document.activeElement;
|
||||||
|
if (ae === input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var tag = ae && ae.tagName ? ae.tagName.toLowerCase() : '';
|
||||||
|
if (tag === 'input' || tag === 'textarea' || tag === 'select' || (ae && ae.isContentEditable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ev.preventDefault();
|
||||||
|
input.focus();
|
||||||
|
input.value = digitsOnly(input.value + ev.key);
|
||||||
|
maybeSubmit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', boot);
|
||||||
|
} else {
|
||||||
|
boot();
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -15,10 +15,7 @@ require_once __DIR__ . '/../src/bootstrap.php';
|
|||||||
|
|
||||||
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
|
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
|
||||||
$base = Auth::basePath();
|
$base = Auth::basePath();
|
||||||
if ($base !== '' && str_starts_with($uri, $base)) {
|
$route = resolve_console_route($uri);
|
||||||
$uri = substr($uri, strlen($base)) ?: '/';
|
|
||||||
}
|
|
||||||
$route = rtrim($uri, '/') ?: '/';
|
|
||||||
|
|
||||||
if ($route === '/api/upload.php' || str_ends_with($route, '/api/upload.php')) {
|
if ($route === '/api/upload.php' || str_ends_with($route, '/api/upload.php')) {
|
||||||
require __DIR__ . '/api/upload.php';
|
require __DIR__ . '/api/upload.php';
|
||||||
@@ -152,7 +149,7 @@ if ($route === '/api/auth_register.php' || str_ends_with($route, '/api/auth_regi
|
|||||||
|
|
||||||
if ($route === '/logout') {
|
if ($route === '/logout') {
|
||||||
Auth::logout();
|
Auth::logout();
|
||||||
header('Location: ' . $base . '/login');
|
header('Location: ' . Auth::authUrl('/login'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +162,7 @@ if ($route === '/login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
if ($result === 'pending_2fa') {
|
if ($result === 'pending_2fa') {
|
||||||
header('Location: ' . $base . '/two-factor');
|
header('Location: ' . Auth::authUrl('/two-factor'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
$loginError = 'Invalid credentials';
|
$loginError = 'Invalid credentials';
|
||||||
@@ -183,7 +180,7 @@ if ($route === '/login' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
|
|
||||||
if ($route === '/login') {
|
if ($route === '/login') {
|
||||||
if (Auth::pending2faUserId() > 0) {
|
if (Auth::pending2faUserId() > 0) {
|
||||||
header('Location: ' . $base . '/two-factor');
|
header('Location: ' . Auth::authUrl('/two-factor'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
require __DIR__ . '/../views/login.php';
|
require __DIR__ . '/../views/login.php';
|
||||||
@@ -235,7 +232,7 @@ if ($route === '/verify-email') {
|
|||||||
|
|
||||||
if ($route === '/two-factor' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($route === '/two-factor' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
if (Auth::pending2faUserId() <= 0) {
|
if (Auth::pending2faUserId() <= 0) {
|
||||||
header('Location: ' . $base . '/login');
|
header('Location: ' . Auth::authUrl('/login'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
$code = trim($_POST['code'] ?? '');
|
$code = trim($_POST['code'] ?? '');
|
||||||
@@ -250,9 +247,10 @@ if ($route === '/two-factor' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
|
|
||||||
if ($route === '/two-factor') {
|
if ($route === '/two-factor') {
|
||||||
if (Auth::pending2faUserId() <= 0) {
|
if (Auth::pending2faUserId() <= 0) {
|
||||||
header('Location: ' . $base . '/login');
|
header('Location: ' . Auth::authUrl('/login'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
$twofaQr = AuthTwoFactorPage::qrPayload();
|
||||||
require __DIR__ . '/../views/two_factor_challenge.php';
|
require __DIR__ . '/../views/two_factor_challenge.php';
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from pathlib import Path
|
|||||||
frag, apps_path = map(Path, sys.argv[1:3])
|
frag, apps_path = map(Path, sys.argv[1:3])
|
||||||
text = apps_path.read_text()
|
text = apps_path.read_text()
|
||||||
start = text.index("server {\n\tlisten 80;")
|
start = text.index("server {\n\tlisten 80;")
|
||||||
end = text.index("\n}\n\n\nserver {\n\tlisten 443")
|
end = text.index("\nserver {\n\tlisten 443")
|
||||||
apps_path.write_text(text[:start] + frag.read_text().strip() + text[end:])
|
apps_path.write_text(text[:start] + frag.read_text().strip() + text[end:])
|
||||||
print(f"Updated listen 80 in {apps_path}")
|
print(f"Updated listen 80 in {apps_path}")
|
||||||
PY
|
PY
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ final class Auth {
|
|||||||
|
|
||||||
public static function check(): void {
|
public static function check(): void {
|
||||||
if (!self::user()) {
|
if (!self::user()) {
|
||||||
header('Location: ' . self::basePath() . '/login');
|
header('Location: ' . self::authUrl('/login'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,4 +141,27 @@ final class Auth {
|
|||||||
$bp = cfg('base_path', '');
|
$bp = cfg('base_path', '');
|
||||||
return rtrim($bp, '/');
|
return rtrim($bp, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Shared project prefix, e.g. /app/androidcast_project */
|
||||||
|
public static function projectBasePath(): string {
|
||||||
|
$p = trim((string) cfg('project_base_path', ''));
|
||||||
|
if ($p !== '') {
|
||||||
|
return rtrim($p, '/');
|
||||||
|
}
|
||||||
|
$bp = self::basePath();
|
||||||
|
if (preg_match('#^(.+)/(?:crashes|issues|graphs|build)$#', $bp, $m)) {
|
||||||
|
return $m[1];
|
||||||
|
}
|
||||||
|
return '/app/androidcast_project';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Login/logout/register/2FA live at project root, not under /issues/. */
|
||||||
|
public static function authUrl(string $path = ''): string {
|
||||||
|
$root = self::projectBasePath();
|
||||||
|
$path = '/' . ltrim($path, '/');
|
||||||
|
if ($path === '/' || $path === '') {
|
||||||
|
return $root;
|
||||||
|
}
|
||||||
|
return $root . $path;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
35
examples/crash_reporter/backend/src/AuthTwoFactorPage.php
Normal file
35
examples/crash_reporter/backend/src/AuthTwoFactorPage.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/** 2FA challenge page helpers (QR short link for mobile handoff). */
|
||||||
|
final class AuthTwoFactorPage {
|
||||||
|
/** @return array{short_url?:string,qr_url?:string,long_url:string}|null */
|
||||||
|
public static function qrPayload(): ?array {
|
||||||
|
$uid = Auth::pending2faUserId();
|
||||||
|
if ($uid <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$project = Auth::projectBasePath();
|
||||||
|
$longUrl = 'https://apps.f0xx.org' . $project . '/?ref=2fa&uid=' . $uid;
|
||||||
|
if (!UrlShortenerDatabase::enabled() || !UrlShortenerDatabase::ping()) {
|
||||||
|
return ['long_url' => $longUrl];
|
||||||
|
}
|
||||||
|
$bearers = ShortLinksRepository::listBearers();
|
||||||
|
if ($bearers === []) {
|
||||||
|
return ['long_url' => $longUrl];
|
||||||
|
}
|
||||||
|
$bearerId = (int) ($bearers[0]['id'] ?? 0);
|
||||||
|
if ($bearerId <= 0) {
|
||||||
|
return ['long_url' => $longUrl];
|
||||||
|
}
|
||||||
|
$mint = ShortLinksRepository::createLink($bearerId, $longUrl, 900);
|
||||||
|
if (empty($mint['ok'])) {
|
||||||
|
return ['long_url' => $longUrl];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
'long_url' => $longUrl,
|
||||||
|
'short_url' => (string) ($mint['short_url'] ?? ''),
|
||||||
|
'qr_url' => (string) ($mint['qr_url'] ?? ''),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -334,8 +334,8 @@ final class GraphRepository {
|
|||||||
return [
|
return [
|
||||||
'hub' => $prefix . '/',
|
'hub' => $prefix . '/',
|
||||||
'graphs' => $prefix . '/graphs/',
|
'graphs' => $prefix . '/graphs/',
|
||||||
'crashes' => $prefix . '/crashes/?view=reports',
|
'issues' => $prefix . '/issues/?view=reports',
|
||||||
'tickets' => $prefix . '/crashes/?view=tickets',
|
'tickets' => $prefix . '/issues/?view=tickets',
|
||||||
'build' => $prefix . '/build/',
|
'build' => $prefix . '/build/',
|
||||||
'git' => $prefix . '/git/',
|
'git' => $prefix . '/git/',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ require_once __DIR__ . '/WireGuardAddressPool.php';
|
|||||||
require_once __DIR__ . '/WireGuardTrafficStats.php';
|
require_once __DIR__ . '/WireGuardTrafficStats.php';
|
||||||
require_once __DIR__ . '/RsshSessionProvisioner.php';
|
require_once __DIR__ . '/RsshSessionProvisioner.php';
|
||||||
require_once __DIR__ . '/RsshBastionProvisioner.php';
|
require_once __DIR__ . '/RsshBastionProvisioner.php';
|
||||||
|
require_once __DIR__ . '/AuthTwoFactorPage.php';
|
||||||
require_once __DIR__ . '/AnalyticsHead.php';
|
require_once __DIR__ . '/AnalyticsHead.php';
|
||||||
|
|
||||||
function cfg(string $key, $default = null) {
|
function cfg(string $key, $default = null) {
|
||||||
@@ -108,6 +109,23 @@ function json_out(array $data, int $code = 200): void {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Resolve web route: auth pages at project root; console pages under base_path. */
|
||||||
|
function resolve_console_route(string $uri): string {
|
||||||
|
$projectRoot = rtrim((string) cfg('project_base_path', '/app/androidcast_project'), '/');
|
||||||
|
$base = rtrim((string) cfg('base_path', ''), '/');
|
||||||
|
if ($projectRoot !== '' && str_starts_with($uri, $projectRoot)) {
|
||||||
|
$suffix = substr($uri, strlen($projectRoot)) ?: '/';
|
||||||
|
$candidate = rtrim(strtok($suffix, '?') ?: '/', '/') ?: '/';
|
||||||
|
if (preg_match('#^/(login|logout|register|two-factor|verify-email)(?:/|$)#', $candidate)) {
|
||||||
|
return $candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($base !== '' && str_starts_with($uri, $base)) {
|
||||||
|
$uri = substr($uri, strlen($base)) ?: '/';
|
||||||
|
}
|
||||||
|
return rtrim(strtok($uri, '?') ?: '/', '/') ?: '/';
|
||||||
|
}
|
||||||
|
|
||||||
/** Public URL prefix for the active console view (graphs vs crashes). */
|
/** Public URL prefix for the active console view (graphs vs crashes). */
|
||||||
function console_base_path(?string $view = null): string {
|
function console_base_path(?string $view = null): string {
|
||||||
$view = $view ?? (string) ($_GET['view'] ?? '');
|
$view = $view ?? (string) ($_GET['view'] ?? '');
|
||||||
@@ -117,6 +135,9 @@ function console_base_path(?string $view = null): string {
|
|||||||
return rtrim($graphs, '/');
|
return rtrim($graphs, '/');
|
||||||
}
|
}
|
||||||
$crashes = rtrim((string) cfg('base_path', ''), '/');
|
$crashes = rtrim((string) cfg('base_path', ''), '/');
|
||||||
|
if (str_ends_with($crashes, '/issues')) {
|
||||||
|
return substr($crashes, 0, -strlen('/issues')) . '/graphs';
|
||||||
|
}
|
||||||
if (str_ends_with($crashes, '/crashes')) {
|
if (str_ends_with($crashes, '/crashes')) {
|
||||||
return substr($crashes, 0, -strlen('/crashes')) . '/graphs';
|
return substr($crashes, 0, -strlen('/crashes')) . '/graphs';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,7 +220,7 @@
|
|||||||
<span class="nav-icon nav-icon--user" aria-hidden="true"></span>
|
<span class="nav-icon nav-icon--user" aria-hidden="true"></span>
|
||||||
<span class="nav-label" data-i18n="nav.security">Security</span>
|
<span class="nav-label" data-i18n="nav.security">Security</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="<?= h(Auth::basePath()) ?>/logout"
|
<a href="<?= h(Auth::authUrl('/logout')) ?>"
|
||||||
class="nav-link nav-link--logout"
|
class="nav-link nav-link--logout"
|
||||||
data-i18n-aria="nav.logout" data-i18n-title="nav.logout"
|
data-i18n-aria="nav.logout" data-i18n-title="nav.logout"
|
||||||
aria-label="Logout"
|
aria-label="Logout"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ $bp = Auth::basePath();
|
|||||||
<body class="live-page" data-base-path="<?= h($bp) ?>" data-view="live_education">
|
<body class="live-page" data-base-path="<?= h($bp) ?>" data-view="live_education">
|
||||||
<main id="live-education-app" class="live-shell">
|
<main id="live-education-app" class="live-shell">
|
||||||
<header class="live-header">
|
<header class="live-header">
|
||||||
<a href="<?= h($bp) ?>/" class="live-brand"><?= h(cfg('app_name')) ?></a>
|
<a href="<?= h(Auth::projectBasePath()) ?>/" class="live-brand">← Back to the console</a>
|
||||||
<span class="tag-pill">Education demo</span>
|
<span class="tag-pill">Education demo</span>
|
||||||
</header>
|
</header>
|
||||||
<section class="card card--lift live-card">
|
<section class="card card--lift live-card">
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ $role = trim((string) ($_GET['role'] ?? ''));
|
|||||||
data-direct="<?= $direct ? '1' : '0' ?>"
|
data-direct="<?= $direct ? '1' : '0' ?>"
|
||||||
data-role="<?= h($role) ?>">
|
data-role="<?= h($role) ?>">
|
||||||
<header class="live-header">
|
<header class="live-header">
|
||||||
<a href="<?= h($bp) ?>/" class="live-brand"><?= h(cfg('app_name')) ?></a>
|
<a href="<?= h(Auth::projectBasePath()) ?>/" class="live-brand">← Back to the console</a>
|
||||||
<span class="tag-pill tag-pill--ok">Live viewer</span>
|
<span class="tag-pill tag-pill--ok">Live viewer</span>
|
||||||
</header>
|
</header>
|
||||||
<section class="card card--lift live-card">
|
<section class="card card--lift live-card">
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
* Digest: SHA256 9bef983f4d34cb790922cda355ccf5d14b565b29273d2ebfe48b3173cd2e8f9f
|
* Digest: SHA256 9bef983f4d34cb790922cda355ccf5d14b565b29273d2ebfe48b3173cd2e8f9f
|
||||||
*/
|
*/
|
||||||
$bp = Auth::basePath();
|
$bp = Auth::basePath();
|
||||||
|
$auth = Auth::authUrl();
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="dark">
|
<html lang="en" data-theme="dark">
|
||||||
@@ -40,7 +41,7 @@ $bp = Auth::basePath();
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<form class="login-card" method="post" action="<?= h($bp) ?>/login">
|
<form class="login-card" method="post" action="<?= h($auth) ?>/login">
|
||||||
<h1><?= h(cfg('app_name')) ?></h1>
|
<h1><?= h(cfg('app_name')) ?></h1>
|
||||||
<p class="muted" data-i18n="login.sign_in_hint">Sign in to view anonymous issue reports</p>
|
<p class="muted" data-i18n="login.sign_in_hint">Sign in to view anonymous issue reports</p>
|
||||||
<?php if (!empty($loginError)): ?>
|
<?php if (!empty($loginError)): ?>
|
||||||
@@ -50,7 +51,7 @@ $bp = Auth::basePath();
|
|||||||
<label><span data-i18n="login.password">Password</span><input name="password" type="password" autocomplete="current-password" required></label>
|
<label><span data-i18n="login.password">Password</span><input name="password" type="password" autocomplete="current-password" required></label>
|
||||||
<button type="submit" data-i18n="login.submit">Sign in</button>
|
<button type="submit" data-i18n="login.submit">Sign in</button>
|
||||||
<p class="hint" data-i18n="login.hint_default">Default: admin / admin</p>
|
<p class="hint" data-i18n="login.hint_default">Default: admin / admin</p>
|
||||||
<p class="hint"><a href="<?= h($bp) ?>/register" data-i18n="login.register">Register</a></p>
|
<p class="hint"><a href="<?= h($auth) ?>/register" data-i18n="login.register">Register</a></p>
|
||||||
</form>
|
</form>
|
||||||
<?php platform_render_footer(); ?>
|
<?php platform_render_footer(); ?>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
$bp = Auth::basePath();
|
$bp = Auth::basePath();
|
||||||
|
$auth = Auth::authUrl();
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="dark">
|
<html lang="en" data-theme="dark">
|
||||||
@@ -29,7 +30,7 @@ $bp = Auth::basePath();
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<form class="login-card" method="post" action="<?= h($bp) ?>/register">
|
<form class="login-card" method="post" action="<?= h($auth) ?>/register">
|
||||||
<h1 data-i18n="register.title">Create account</h1>
|
<h1 data-i18n="register.title">Create account</h1>
|
||||||
<p class="muted" data-i18n="register.hint">We will email a verification link before you can sign in.</p>
|
<p class="muted" data-i18n="register.hint">We will email a verification link before you can sign in.</p>
|
||||||
<?php if (!empty($registerError)): ?>
|
<?php if (!empty($registerError)): ?>
|
||||||
@@ -45,7 +46,7 @@ $bp = Auth::basePath();
|
|||||||
<label><span data-i18n="register.password">Password</span>
|
<label><span data-i18n="register.password">Password</span>
|
||||||
<input name="password" type="password" autocomplete="new-password" minlength="10" required></label>
|
<input name="password" type="password" autocomplete="new-password" minlength="10" required></label>
|
||||||
<button type="submit" data-i18n="register.submit">Register</button>
|
<button type="submit" data-i18n="register.submit">Register</button>
|
||||||
<p class="hint"><a href="<?= h($bp) ?>/login" data-i18n="register.back_login">Back to sign in</a></p>
|
<p class="hint"><a href="<?= h($auth) ?>/login" data-i18n="register.back_login">Back to sign in</a></p>
|
||||||
</form>
|
</form>
|
||||||
<?php platform_render_footer(); ?>
|
<?php platform_render_footer(); ?>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
$bp = Auth::basePath();
|
$bp = Auth::basePath();
|
||||||
|
$auth = Auth::authUrl();
|
||||||
|
$twofaQr = AuthTwoFactorPage::qrPayload();
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="dark">
|
<html lang="en" data-theme="dark">
|
||||||
@@ -17,6 +19,7 @@ $bp = Auth::basePath();
|
|||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" href="<?= h($bp) ?>/assets/css/app.css">
|
<link rel="stylesheet" href="<?= h($bp) ?>/assets/css/app.css">
|
||||||
<script src="<?= h($bp) ?>/assets/js/i18n.js" defer></script>
|
<script src="<?= h($bp) ?>/assets/js/i18n.js" defer></script>
|
||||||
|
<script src="<?= h($bp) ?>/assets/js/two_factor.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">
|
<div class="login-locale">
|
||||||
@@ -28,16 +31,22 @@ $bp = Auth::basePath();
|
|||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<form class="login-card" method="post" action="<?= h($bp) ?>/two-factor">
|
<form class="login-card" method="post" action="<?= h($auth) ?>/two-factor" id="twofa-form">
|
||||||
<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>
|
||||||
<?php if (!empty($twofaError)): ?>
|
<?php if (!empty($twofaError)): ?>
|
||||||
<div class="alert" data-i18n="twofa.error"><?= h($twofaError) ?></div>
|
<div class="alert" data-i18n="twofa.error"><?= h($twofaError) ?></div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
<?php if (is_array($twofaQr) && !empty($twofaQr['qr_url'])): ?>
|
||||||
|
<figure class="twofa-qr-wrap">
|
||||||
|
<img src="<?= h((string) $twofaQr['qr_url']) ?>" width="160" height="160" alt="QR code for mobile sign-in link" class="twofa-qr">
|
||||||
|
<figcaption class="muted">Scan to open the hub on your phone<?php if (!empty($twofaQr['short_url'])): ?> · <a href="<?= h((string) $twofaQr['short_url']) ?>" rel="noopener"><?= h((string) $twofaQr['short_url']) ?></a><?php endif; ?></figcaption>
|
||||||
|
</figure>
|
||||||
|
<?php endif; ?>
|
||||||
<label><span data-i18n="twofa.code">Authentication code</span>
|
<label><span data-i18n="twofa.code">Authentication code</span>
|
||||||
<input name="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" autocomplete="one-time-code" required autofocus></label>
|
<input id="twofa-code-input" name="code" inputmode="numeric" pattern="[0-9]{6}" maxlength="6" autocomplete="one-time-code" required autofocus></label>
|
||||||
<button type="submit" data-i18n="twofa.submit">Continue</button>
|
<button type="submit" data-i18n="twofa.submit">Continue</button>
|
||||||
<p class="hint"><a href="<?= h($bp) ?>/login" data-i18n="twofa.cancel">Cancel</a></p>
|
<p class="hint"><a href="<?= h($auth) ?>/login" data-i18n="twofa.cancel">Cancel</a></p>
|
||||||
</form>
|
</form>
|
||||||
<?php platform_render_footer(); ?>
|
<?php platform_render_footer(); ?>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
$bp = Auth::basePath();
|
$bp = Auth::basePath();
|
||||||
|
$auth = Auth::authUrl();
|
||||||
?>
|
?>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@@ -21,10 +22,10 @@ $bp = Auth::basePath();
|
|||||||
<h1 data-i18n="verify.title">Email verification</h1>
|
<h1 data-i18n="verify.title">Email verification</h1>
|
||||||
<?php if (!empty($verifyOk)): ?>
|
<?php if (!empty($verifyOk)): ?>
|
||||||
<p class="alert alert--ok" data-i18n="verify.ok">Your email is verified. You can sign in and enroll two-factor authentication.</p>
|
<p class="alert alert--ok" data-i18n="verify.ok">Your email is verified. You can sign in and enroll two-factor authentication.</p>
|
||||||
<p><a class="btn" href="<?= h($bp) ?>/login" data-i18n="verify.sign_in">Sign in</a></p>
|
<p><a class="btn" href="<?= h($auth) ?>/login" data-i18n="verify.sign_in">Sign in</a></p>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<p class="alert" data-i18n="verify.fail"><?= h($verifyError ?? 'Invalid or expired link.') ?></p>
|
<p class="alert" data-i18n="verify.fail"><?= h($verifyError ?? 'Invalid or expired link.') ?></p>
|
||||||
<p class="hint"><a href="<?= h($bp) ?>/register" data-i18n="verify.register_again">Register again</a></p>
|
<p class="hint"><a href="<?= h($auth) ?>/register" data-i18n="verify.register_again">Register again</a></p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
<?php platform_render_footer(); ?>
|
<?php platform_render_footer(); ?>
|
||||||
|
|||||||
Reference in New Issue
Block a user