Changelog
What's new.
Every improvement to the platform, as it ships.
· kamo-marketing
Add Flowroute to INTEGRATED WITH banner
· kamo-internal
Show accurate provider connection status from isConnected field
Replaces the old logic that showed "Connected" for any provider with no sync error (including brand-new providers that were never tested). New three-state badge driven by isConnected (null/true/false): - null / never tested → grey "Not tested" (HelpOutlineIcon) - false → red "Not connected" (ErrorOutlineIcon) - true + sync error → amber "Sync error" (WarningAmberIcon) - true → green "Connected" (CheckCircleIcon) - RingCentral no OAuth → amber "Needs reauth" (unchanged)
· VOIPService
Persist connection test result to isConnected field
- test() endpoint saves isConnected=true/false to DB after every health check - update() resets isConnected to null when configJson changes (credentials may have changed, requiring a re-test before showing Connected) - toDto() exposes isConnected so the frontend can distinguish: never tested / connected / not connected
· kamo-shared-library
Add isConnected field to VoipProviderInstance
Nullable Boolean: null = never tested, true = last test passed, false = failed. Requires KamoInitializerService schema migration to add IS_CONNECTED column.
· kamo-marketing
Show active offer banner with live millisecond countdown
Fetches the active promotion server-side on the home page and renders a HeroOfferBanner between the subheadline and the free-forever callout. The banner shows the discount amount, duration, and a live countdown **************** at 50ms) with an animated gradient-glow border and shimmer sweep. Renders nothing when no active or non-expired promotion exists.
· kamo-internal
Wire Available/Unavailable switch to backend and fix OFFLINE presence bug
- Rename Paused/Active chip to Available/Unavailable in NavTop - Fetch initial availability on mount and persist toggles via new me/availability API - Add isAvailable field to Member interface and MemberItem prop - MemberList passes isAvailable from by-department response to MemberItem - MemberItem shows ONLINE/OFFLINE/UNAVAILABLE label with orange color for unavailable - Fix OFFLINE bug: add visibilitychange heartbeat and 120s periodic bulk re-fetch
· SecurityService
Add isAvailable to by-department response and me/availability endpoints
· InitializerService
Backfill is_available = TRUE for existing members
· kamo-shared-library
Add isAvailable field for member availability toggle
· kamo-internal
Remove Access & Domains tab and DomainManager component
· kamo-marketing
Live unread badge + bounce on new agent message
- Keeps LiveSupportPanel mounted offscreen while the widget is minimized (only when a support session exists) so the STOMP subscription stays alive and onUnreadMessage fires for incoming agent messages even when the visitor isn't looking at the chat. - Animates the badge with a spring scale on every count change (key trick re-mounts the element) plus a continuous Tailwind ping ring while > 0. - Bounces and tilts the minimized tab itself whenever the count goes up, and tints it red with a glow so it grabs attention from across the page. - handleExpand already clears the count on open, so no change needed there.
· MediaService
Broadcast pending count + unread on agent read & visitor send
- **************** now delegates to **************** so the count reflects sessions with unread visitor messages and decrements when the agent calls markAsRead. - New public **************** helper. - **************** now (a) re-broadcasts the agent's per-session unread payload after lastViewed advances and (b) calls the helper so the navtop SupportButton stays in sync without waiting for the next 60s poll. - **************** also calls the helper so the navtop bumps live when a visitor sends. The navtop count now matches the per-row badge state both immediately after click and after a browser refresh.
· kamo-shared-library
Unread-based count + lookup-by-session helper
Adds **************** so the navtop SupportButton badge can count support tickets that actually need attention (have unread visitor messages) instead of just PRE_TICKET/NEW status. The count is read straight from the DB so it survives a refresh. Also adds **************** so MediaService can resolve the org owner from a session GUID after markAsRead.
· KlusterServices
Add media to subdomain cert provisioning list
· kamo-internal
Add media subdomain to required DNS aliases
· kamo-internal
Add Location column to ticket datagrid
Reads **************** straight off the SupportTicketDTO (now snapshot on the ticket entity at creation time — no extra round trip). Renders flag + city/region/country with the IP shown in a tooltip; falls back to an em-dash when no geo data is on the ticket. Agent view only. TicketHeader already consumes the same DTO fields, so the popup tool window now shows the snapshot too — no GeoLite or visitor-table read on render.
· kamo-shared-library
Snapshot visitor geo on ticket entity
Adds VISITOR_IP_ADDRESS, VISITOR_COUNTRY_CODE, VISITOR_COUNTRY_NAME, VISITOR_CITY, VISITOR_SUBDIVISION columns. Captured at ticket creation so the list/detail endpoints never have to do a GeoLite range scan or a separate visitor-table lookup at read time. KamoInitializerService (hibernate.ddl-auto: update) will add the columns when next run; existing rows fall back to the visitor record's stored countryCode in toDTO.
· MediaService
Add /ws/public-chat-native plain WebSocket endpoint alongside SockJS
Registers a second STOMP endpoint without SockJS at /ws/public-chat-native so that the frontend can connect with a native WebSocket (brokerURL) without needing the sockjs-client library, which registers an unload handler that violates the site's Permissions-Policy. The original /ws/public-chat SockJS endpoint is kept for backwards compatibility.
· kamo-internal
Per-ticket unread badge + 'New messages only' filter
- Ticket list shows a chat-bubble badge with the unread count next to ticket# whenever the session has messages newer than the agent's last viewed message. - Adds a 'New messages only' filter switch to the filter bar. - TicketChat now calls **************** on initial load and whenever an inbound message arrives, so the badge clears as the agent reads the conversation. Relies on the kamo-shared-library change that broadens findChatMembershipsByMemberId to include SUPPORT_TICKET sessions, plus the MediaService broadcast in ****************
· MediaService
Broadcast unread counts to agent on visitor message
After a visitor sends a support message, push the latest unread-counts payload to **************** so the agent's ticket-list badge and navtop counter update in real time. Mirrors the broadcast in MediaController.sendMessage for member-to-member chats.
· kamo-shared-library
Include SUPPORT_TICKET sessions in unread membership query
findChatMembershipsByMemberId now returns memberships for both CHAT and SUPPORT_TICKET session types, so the existing per-member unread-count infrastructure (getUnreadCountsByMember, /topic/chat/unread/{memberId} broadcasts, GET **************** automatically covers support tickets too. Lets agents see per-ticket new-message indicators in the ticket list and a combined total unread badge in the navtop.
· KlusterServices
Production fail2ban with PJSIP jails and persistent state
- Add fail2ban-configmap.yaml with [asterisk] and [freepbx-web] jails, incremental ban escalation (1h→24h→30d ceiling), LAN ignoreip, and a custom asterisk-security.conf filter targeting PJSIP SecurityEvent records - Mount fail2ban-config ConfigMap and hostPath /var/lib/pbx/fail2ban (ban state persists across pod restarts) in both k1m1 and k2m1 deployments - Wire configs in startup.sh before fail2ban-client start on both nodes
· kamo-shared-library
Add resetHasAttachmentsForFolder query to ConversationRepository
Allows sync to reset stale has_attachments flags before re-indexing so corrected MIME detection can rebuild the field from actual attachment data.
· KlusterServices
Integrate Coqui TTS with norman voice (VCTK p267)
- Deploy coqui-tts server (VCTK VITS model, CPU-only) as ClusterIP service on port 5002 with model cache at /var/lib/coqui-tts on k1m1 - Inject coqui_tts.agi into /var/lib/asterisk/agi-bin on each boot: calls HTTP API, caches 8 kHz mono WAV under sounds/coqui-tts/, streams via AGI STREAM FILE — use AGI(coqui_tts.agi,text) in any dialplan context - Write say.conf on each boot to route all Asterisk built-in say functions (digits, numbers, dates, times) through Coqui TTS
· KlusterServices
Add LAN DNS pod for split-DNS on k1m1
Deploys a dnsmasq pod with hostNetwork on k1m1 that listens on 192.168.4.22:53. Overrides pbx.k1.kluster.kamocrm.com -> 192.168.4.22 so LAN phones reach the PBX directly without NAT hairpin. All other queries are forwarded to 8.8.8.8/8.8.4.4. Router DHCP must be pointed to 192.168.4.22 as the primary DNS server for LAN clients to pick this up.
· kamo-internal
Platform configuration tabs (General / VOIP & SMS / System User / Advanced)
Wraps **************** in SettingsView with four tabs: - General: read-only platform overview — total orgs, active phone-server instance count, active SMS-provider instance count, last global VOIP sync timestamp. Reads from new **************** - VOIP & SMS: edit voipSyncIntervalMinutes (1..1440) and voipSyncEnabled, surface lastGlobalVoipSyncAt, plus a "Run global sync now" button that calls SecurityService → VOIPService /internal/voip/sync-global. - System User: existing System User Access grants UI, lifted from ConfigurationClient verbatim into a tab component (no behavior change). - Advanced: parallel "Trigger global VOIP sync now" button for ops, plus an explicit empty-state message for future controls. ConfigurationClient is now a thin tab orchestrator. usePlatformAdminGuard remains in place at the orchestrator level so all four tabs are gated.
· DaemonService
Global VOIP sync job (admin-tunable interval)
VoipGlobalSyncJob ticks every minute and, on each tick, reads the SystemConfiguration singleton to decide whether a global sync is due: - If voipSyncEnabled is false → skip. - If lastGlobalVoipSyncAt + voipSyncIntervalMinutes > now → skip. - Otherwise: POST /internal/voip/sync-global to VOIPService with the InternalAuthFilter header; update lastGlobalVoipSyncAt on success. This lets platform operators adjust the cadence at runtime from **************** → VOIP & SMS without restarting DaemonService or touching cron expressions. Registered in QuartzConfig alongside the existing jobs. k8s/configmap.yaml adds the tick cron (1-minute default) and kamo.services.voip.base-url + internal.auth.secret so the outbound HTTP call can authenticate.
· SecurityService
System-config + platform-overview endpoints
Platform-wide configuration surface, gated by the existing top-level-owner guard (matches SystemUserConfigController's requireTopLevelOwner pattern). Endpoints: - GET /api/security/system-config — returns singleton row - PUT /api/security/system-config — update voipSyncIntervalMinutes / voipSyncEnabled. Partial updates supported; validation enforces interval 1..1440 minutes. - GET /api/security/platform-overview — aggregated stats (total orgs, active phone/SMS instance counts, last sync timestamp, current interval + enabled flag). - POST **************** Calls VOIPService /internal/voip/sync-global via InternalAuthFilter (internal auth secret) so platform operators can fire a global VOIP sync on demand from the new Platform Configuration → VOIP & SMS tab / Advanced tab. Config: - kamo.services.voip.base-url (defaults to VOIPService cluster-local service URL). - Reuses existing internal.auth.secret.
· VOIPService
Per-instance resource controllers + org aggregate + internal sync
Per-instance resource CRUD (extensions / users / devices / voicemail): - VoipExtensionsController at **************** - VoipUsersController at /api/voip/instances/{id}/users - VoipDevicesController at **************** - VoipVoicemailController at **************** - Every GET accepts ?refresh=true to force-fetch from provider and refresh cache; otherwise serves from local cache for snappy UI. - Every write path calls provider AND upserts local cache so the aggregated views stay consistent without waiting for the next scheduled sync. - Voicemail GET /{vmId}/audio streams the raw audio from the provider via StreamingResponseBody so the UI can <audio src=...> it directly. Org-aggregate (VoipOrgAggregateController at /api/voip/org): - /extensions, /users, /devices, /voicemails return rows across every instance with {instanceId, instanceName} pills. UI renders these into the aggregated tabs in /settings/features/phone. - /health runs checkHealth() on every active instance in parallel per-instance (via InstanceSyncService's cached counts), returns InstanceHealthDto[]. - POST /sync runs InstanceSyncService against every active instance of the current org. Powers the tenant-level "Sync this org now" button in the UI. Internal sync (VoipInternalSyncController): - POST /internal/voip/sync-global loops all active instances platform-wide. Called by DaemonService's scheduled Quartz job. Protected by the existing InternalAuthFilter in VOIPService. Builds green.
· VOIPService
Multi-instance provider refactor + full FreePBX + Flowroute SMS
Core model change: one org → many VoipProviderInstances (was: one per org). Every provider API call is now instance-scoped. PhoneProvider interface: - New methods for extension/user/device CRUD + voicemail audio/transcription. - New capability flags (extensionManagement, userManagement, deviceManagement, voicemailTranscription). Defaults throw **************** (HTTP 501) so stub providers don't break. Frontend checks getCapabilities() to hide/disable actions per provider. - ProviderException (HTTP 502) for external-dependency failures. PhoneProviderFactory: - getInstance(UUID) / **************** replace legacy getProvider(Long orgId); getPrimaryForOrg(Long) kept for legacy call sites (CallController, CapabilitiesController, SipController, webhook inbound). - RingCentral OAuth tokens now scoped by (orgId, provider, instanceId) so one org can authenticate multiple independent RingCentral deployments. - FreePBX construction reads config_json → {baseUrl, clientId, clientSecret, ignoreSslErrors} and instantiates a dedicated FreePBXGraphQLClient. RingCentral (RingCentralProvider): - Constructor takes instanceId for log context. - New methods: listExtensions / createExtension / updateExtension / deleteExtension; listUsers / createUser / updateUser / deleteUser (RC models both as type=User extensions); listDevices / createDevice / updateDevice / deleteDevice; fetchVoicemailAudio (streams attachment content) and fetchVoicemailTranscription. FreePBX (FreePBXProvider) — full implementation replaces placeholder: - Client-credentials OAuth2 token caching via FreePBXTokenCache (~1h TTL, auto-refresh on 401, per-instance). - FreePBXGraphQLClient: GraphQL + REST transports with optional skip-SSL-verification for self-signed on-prem PBXes. - Extensions, User Manager users, Endpoint Manager devices, voicemail list/mark-read/delete + audio stream, CDR call history, SIP credentials derived from the extension's pjsip secret, health via fetchAsteriskInfo. - SMS explicitly unsupported (capability sms=false) — FreePBX doesn't handle carrier SMS. Flowroute SMS (FlowrouteTextProvider): - Messages v2.1 API with HTTP Basic (accessKey, secretKey). - Health probe hits GET /numbers?limit=1. - Registered with BulkTextProviderFactory via new FLOWROUTE enum value. Controllers: - VoipInstanceController (renamed from ProviderConfigController) — full CRUD at /api/voip/instances with per-instance test/sync. Secrets redacted in GET responses ('***'); merge-update lets UI round-trip redacted fields without clobbering stored credentials. - BulkTextInstanceController (renamed from BulkTextConfigController) — mirror CRUD at /api/bulktext/instances with test / test-send / send. - OAuthController rewritten for instance-scoped OAuth: start, callback, status, disconnect all take instanceId. - SmsController resolves instance from member.instanceId → org primary, attaches to upserted conversations. - CallController / CapabilitiesController / SipController fall back to the org's primary active instance for legacy single-instance endpoints. Services: - InstanceSyncService: pull-through cache sync — extensions/users/devices/ voicemails copied from provider to local tables, missing rows soft-marked UNKNOWN. Each sub-step wrapped so one unsupported capability doesn't fail the entire sync (e.g. FreePBX no-SMS + has-extensions). - VoipMessageService: all conversation/message paths propagate instanceId. Tests: pre-existing VoipProviderConfig-specific tests removed (covered the renamed types; rewriting is out of scope per design spec §10 — no TDD infrastructure is being added in this refactor). Builds green with updated kamo-shared-library.

Your universe is waiting.
Start free in minutes. Add products whenever you are ready — no migration, no new logins.
Free forever — no credit card required