- 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
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
- 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
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.
AgentService now replaces 'localhost' in the server URL with
sysadmin.lan before sending to the remote agent. Also added
URL/hostname debug output to the agent startup log.
Installer now detects at runtime if it can get root access:
- Root/NOPASSWD sudo: installs to system directories with systemd
- No root: installs to ~/.local/bin with @reboot cron job
Self-elevation uses 'script -qc sudo bash' for requiretty, or falls
back to user mode without hanging on password prompts.
Uninstaller checks both system and user paths.
- agent-install.sh and agent-uninstall.sh now detect if running as root
at startup and re-execute via 'script -qc sudo bash' if needed
- AgentService commands simplified: just write to /tmp and run bash,
the scripts handle privilege escalation internally
- Fixes exit code propagation: the script's exit code now correctly
flows back through exec -> script -> sudo -> bash -> SSH
- Revert SSHService exec() back to original (remove PTY parameter)
- AgentService now writes installer to /tmp/ first, then runs via
script -q -c to create a PTY for sudo (handles requiretty)
- Falls back to direct sudo bash if script is unavailable
- Cleans up temp files after execution
- Added mkdir -p for /usr/local/bin in installer
- Added file verification and byte-count after writing agent binary
The remote server has 'Defaults requiretty' in sudoers, which prevents
sudo from running without a TTY. Modified SSHService::exec() to accept
an optional parameter that allocates a pseudo-terminal, and
extracts the exit status from output when PTY is used (since phpseclib
doesn't return exit status in PTY mode).
The sudo -n check was incorrectly detecting that sudo required a
password, causing fallback to plain bash without root permissions.
Now we always run the installer via sudo bash, consistent with how
all other SSH commands work (systemctl, reboot, etc.).
- AgentService: try sudo -n first, fall back to bash without sudo
- AgentService: add sudo to uninstall command
- AgentService: trim error output to last 20 lines for readability
- View: show SSH output inline in error toast
- Fix undefined $path variable in success path
- Add database migration 006 for agent_key, agent_installed, agent_install_path, agent_last_seen_at columns
- Add Server model methods: findByAgentKey, generateAgentKey, regenerateAgentKey, updateAgentLastSeen, setAgentInstalled
- Auto-generate agent_key on server creation
- Create AgentService for SSH-based install/uninstall of remote agent
- Create bin/agent.sh - bash agent script for remote servers
- Create install/agent-install.sh and install/agent-uninstall.sh
- Create install/servermanager-agent.service systemd unit
- Add POST /api/metrics/push endpoint (auth via agent_key)
- Add web routes for agent install/uninstall/regenerate-key
- Add agent status section to server detail view with install/uninstall modals
- Make MonitoringService::saveMetrics() public for reuse
- Server::getAccessibleServerIds(): super_admin ve todos los servidores;
admin y operator solo ven propios, asignados directos o por equipo
- DashboardController: unifica lógica usando getAccessibleServerIds()
- ServerController::metrics(): agrega control de acceso requireServerAccess()
- ApiController::status(): filtra estadísticas por servidores accesibles
- Android: bump versionCode 9, versionName 1.7.0
- API: --all flag in systemctl list-units to show inactive services
- API: removed head -50 limit
- Android: LinearProgressIndicator while service action is running
- Fix 500 error: cast to string before preg_replace
- Search bar to filter services by name/description
- Redesigned service action modal with status badge + colored buttons
- Version 1.6.0
- Services tab: start/stop/restart/reload via dialog
- Backend: POST /api/servers/:id/service-action
- Removed duplicate APK download from dashboard view
- Version bump to 1.5.0
- Dashboard muestra version del gradle (no API)
- Header sin iconos duplicados (notifications solo en bottom nav)
- Bottom nav: Dashboard, Servers, Alerts (sin Profile ni Logout)
- ServerDetail: tab Services con lista de systemd services
- ServerDetail: tab Actions con Test/Reboot/Shutdown
- Backend: API endpoints para services, reboot, shutdown, test
- Teams index: cards with animation, search filter
- Team detail: user autocomplete via AJAX (search on type)
- CSS: card grid styles, autocomplete dropdown
- Backend: searchUsers endpoint, filter support
- NotificationPoller polls every 30s while app is in foreground
- Poller auto-starts on resume, stops on pause
- New NotificationsScreen with mark-as-read, mark-all-read, infinite scroll
- Bottom nav: Alerts tab with unread badge
- Dashboard: bell icon with unread badge
- Backend: GET /api/notifications/unread-count endpoint
- App version 1.1.0