relay UI overhaul, favicon, settings, admin last-seen, fixes
Relay:
- Inbox/shared/sent split into own view-inbox (nav button + poll).
Compose is now single-column, centered.
- Header logo links to compose. Inbox badge stays fresh on all
views via a quiet loadInbox on every view switch.
- Mobile menu: full-height sheet below header, larger buttons
(15px font, 14px padding), full width.
- Login form wrapped in <form> with name attributes + for/id on
labels so password managers can autofill.
- Admin user list shows "last seen <relative time>" per user.
last_seen_at column on tenants, updated on every authenticated
request (session + client API).
- Cache-busting on style.css + app.js via filemtime.
- View persistence in localStorage (survives reload).
- Shared-bucket cards show title only (preview line removed).
Favicon:
- favicon.svg added to project root + copied to public/, relay/,
website/. Linked via <link rel="icon" type="image/svg+xml"> in
all three <head> sections.
Settings:
- New toggle: "Show shopping list items in task collector"
(default off). collectTasks skips #einkauf/#shopping cards when
disabled.
Fixes:
- card-spinner gets display:inline-block so it rotates outside
flex containers (shopping save spinner).
- Tim tab test opens the plugin before checking active state.
- Compose view centered with margin:auto on desktop.
Bumps to 1.9.29.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
8e738ed9d4
commit
d50f9fe789
19 files changed
+209
-101
No files matched your search
+9
-1
@@ -24,7 +24,9 @@ function getSessionTenant(PDO $db): ?array {
|
||||
if (!$token) return null;
|
||||
$stmt = $db->prepare("SELECT t.* FROM sessions s JOIN tenants t ON t.id = s.tenant_id WHERE s.id = :tok AND s.expires_at > datetime('now')");
|
||||
$stmt->execute(['tok' => $token]);
|
||||
return $stmt->fetch() ?: null;
|
||||
$tenant = $stmt->fetch() ?: null;
|
||||
if ($tenant) touchLastSeen($db, (int)$tenant['id']);
|
||||
return $tenant;
|
||||
}
|
||||
|
||||
function destroySession(PDO $db): void {
|
||||
@@ -62,5 +64,11 @@ function authClient(PDO $db): ?array {
|
||||
$tenant = getTenantByUsername($db, $username);
|
||||
if (!$tenant) return null;
|
||||
if (!password_verify($key, $tenant['secret_hash'])) return null;
|
||||
touchLastSeen($db, (int)$tenant['id']);
|
||||
return $tenant;
|
||||
}
|
||||
|
||||
function touchLastSeen(PDO $db, int $tenantId): void {
|
||||
$db->prepare("UPDATE tenants SET last_seen_at = datetime('now') WHERE id = :id")
|
||||
->execute(['id' => $tenantId]);
|
||||
}
|
||||
@@ -133,6 +133,14 @@ $db->exec('CREATE TABLE IF NOT EXISTS tenants (
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
)');
|
||||
|
||||
// last_seen_at: updated on every authenticated request so the admin
|
||||
// can see when a user was last active.
|
||||
$tenantCols = $db->query("PRAGMA table_info(tenants)")->fetchAll();
|
||||
$tenantColNames = array_column($tenantCols, 'name');
|
||||
if (!in_array('last_seen_at', $tenantColNames)) {
|
||||
$db->exec("ALTER TABLE tenants ADD COLUMN last_seen_at TEXT");
|
||||
}
|
||||
|
||||
$db->exec('CREATE TABLE IF NOT EXISTS settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
|
||||
@@ -14,7 +14,7 @@ if (!defined('RELAY_BOOTSTRAPPED')) { http_response_code(404); exit; }
|
||||
|
||||
if ($route === '/api/users' && $method === 'GET') {
|
||||
if (!$sessionTenant || !$sessionTenant['is_admin']) jsonResponse(['error' => 'admin only'], 403);
|
||||
$rows = $db->query('SELECT id, username, is_admin, created_at FROM tenants ORDER BY id ASC')->fetchAll();
|
||||
$rows = $db->query('SELECT id, username, is_admin, created_at, last_seen_at FROM tenants ORDER BY id ASC')->fetchAll();
|
||||
jsonResponse(['users' => $rows]);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user