- LoginViewModel now calls Tasks.await(FirebaseMessaging.getInstance().token)
to actively fetch the FCM token if not yet saved in prefs
- Previous code relied solely on onNewToken callback, which may not fire
before the user logs in on a fresh install
- FCMService now supports both HTTP v1 API (service account) and legacy API (server key)
- v1 API uses OAuth2 JWT auth with Firebase service account; tries this first
- Falls back to legacy HTTP API if only server key is configured
- Removes invalid tokens from DB automatically (NotRegistered, INVALID_ARGUMENT, etc.)
- AdminController now logs push result status and shows in UI response
- .env updated with FCM_SERVER_KEY placeholder and docs
- Migration 010 run in dev
- google-services.json now has real Firebase project values
- Added firebase-messaging-ktx dependency (was missing)
- Updated google-services plugin to 4.4.4
Backend:
- New user_fcm_tokens table (migration 010) for storing device tokens
- FCMService sends push via Firebase HTTP legacy API (server key from .env)
- POST /api/fcm/register endpoint for Android to register its FCM token
- AdminController::sendNotification() triggers FCM push after DB insert
- FCM config added to config.php
Android:
- Firebase Cloud Messaging service (ServerManagerFirebaseService)
receives push notifications and shows them via NotificationHelper
- FCM token registered with backend on new token and on login
- google-services.json template (must be replaced with real Firebase config)
- firebase-setup.sh script with configuration instructions
- FCM dependency + google-services plugin added to Gradle
- Bump to v1.8.0 (versionCode 12)
- LoginViewModel now calls Tasks.await(FirebaseMessaging.getInstance().token)
to actively fetch the FCM token if not yet saved in prefs
- Previous code relied solely on onNewToken callback, which may not fire
before the user logs in on a fresh install
- FCMService now supports both HTTP v1 API (service account) and legacy API (server key)
- v1 API uses OAuth2 JWT auth with Firebase service account; tries this first
- Falls back to legacy HTTP API if only server key is configured
- Removes invalid tokens from DB automatically (NotRegistered, INVALID_ARGUMENT, etc.)
- AdminController now logs push result status and shows in UI response
- .env updated with FCM_SERVER_KEY placeholder and docs
- Migration 010 run in dev
- google-services.json now has real Firebase project values
- Added firebase-messaging-ktx dependency (was missing)
- Updated google-services plugin to 4.4.4
Backend:
- New user_fcm_tokens table (migration 010) for storing device tokens
- FCMService sends push via Firebase HTTP legacy API (server key from .env)
- POST /api/fcm/register endpoint for Android to register its FCM token
- AdminController::sendNotification() triggers FCM push after DB insert
- FCM config added to config.php
Android:
- Firebase Cloud Messaging service (ServerManagerFirebaseService)
receives push notifications and shows them via NotificationHelper
- FCM token registered with backend on new token and on login
- google-services.json template (must be replaced with real Firebase config)
- firebase-setup.sh script with configuration instructions
- FCM dependency + google-services plugin added to Gradle
- Bump to v1.8.0 (versionCode 12)
- is_read and read_at columns are now obsolete since notification_reads
tracks per-user read receipts
- Removed UPDATE statements in markAsRead/markAllAsRead that touched
the old columns
- API response still includes computed is_read field via SQL CASE in
getForUser() JOIN with notification_reads, maintaining Android
compatibility
- Migration 009 drops both columns
- New notification_reads table (migration 008) with unique (notification_id, user_id)
- Notification model: markAsRead inserts into notification_reads; getForUser JOINs
to show per-user read status; getUnreadCount checks NOT EXISTS in reads table
- getAll() includes read_count subquery for admin view
- Admin view shows 'X / Y' read count for broadcast notifications,
'Read'/'Unread' badge for user-targeted ones
- Migrates existing read notifications into notification_reads
- AGENTS.md updated with new conventions
- Removed the local old() function definition from views/servers/create.php
which conflicted with the global old() in src/Helpers/functions.php:37
- Replaced all old() calls with direct htmlspecialchars([...]) lookups
- Wrap ServerPermission queries in try-catch so show/edit don't 500
when the migration hasn't been run yet
- Same protection for save calls in store/update (both web and API)
- New server_permissions table (migration 007) stores required SSH permissions per server
- PermissionValidator service connects via SSH and validates each permission before save
- ServerPermission model for CRUD on server_permissions table
- Web flow: create/edit forms include dynamic permission rows with quick-add presets
- API flow: serverAdd/serverUpdate accept permissions array, return 400 on failure
- Show view displays required permissions on server detail page
- Form input preserved on validation failure (credentials excluded)
- AGENTS.md updated with new conventions
Scaffold's innerPadding reserved NavigationBar height even when the
bar was hidden (on detail screens). Content like the services list
had extra empty space at the bottom.
Fix: build custom PaddingValues that only includes bottom padding
when inLoggedInArea is true.
Root cause: LoginViewModel.logout() was async (viewModelScope.launch),
so _uiState.isConnected remained true after navigating to login.
LoginScreen's LaunchedEffect immediately navigated back to dashboard,
where APIs failed with 401 again → infinite loop.
Fixes:
- SessionManager: add _invalidating flag to prevent concurrent 401 handling
- AuthInterceptor: check isSessionValid before invalidating
- LoginViewModel: update _uiState synchronously in logout(), clear token
before launching coroutine for prefs.clear()
- LoginViewModel: call SessionManager.restoreSession() after successful login
- MainActivity: remove restoreSession() from logout handler (was incorrectly
re-validating session right after invalidation)
Bump to v1.7.1 (versionCode 10)
Some locales use comma as decimal separator (e.g., es_DO.UTF-8).
This caused invalid JSON like "cpu": 2,4 instead of "cpu": 2.4.
Prefixed CPU and RAM commands with LC_ALL=C to force dot decimals.
Also removed debug logging from pushMetrics endpoint.
The inner PAYLOAD heredoc had a quoted delimiter ('PAYLOAD') which
prevented runtime variable expansion. Changed to unquoted (PAYLOAD)
so $AGENT_KEY, $CPU, etc. are correctly expanded at runtime.
The double-escaping works as:
install time (outer EOF): $AGENT_KEY → (in file)
runtime (inner PAYLOAD): → abc123... (actual value)
The unquoted EOF heredoc expands all $ at install time. Runtime
variables inside functions (CPU, RAM, AGENT_KEY, etc.) must be
escaped with $ to prevent premature expansion.
Also removed the fragile sed replace step entirely — values are now
embedded directly when the heredoc is expanded.