Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .local.env
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
SENTRIUS_VERSION=1.1.325
SENTRIUS_VERSION=1.1.334
SENTRIUS_SSH_VERSION=1.1.41
SENTRIUS_KEYCLOAK_VERSION=1.1.53
SENTRIUS_AGENT_VERSION=1.1.42
SENTRIUS_AI_AGENT_VERSION=1.1.263
LLMPROXY_VERSION=1.0.78
LAUNCHER_VERSION=1.0.82
AGENTPROXY_VERSION=1.0.75
AGENTPROXY_VERSION=1.0.85
4 changes: 2 additions & 2 deletions .local.env.bak
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
SENTRIUS_VERSION=1.1.325
SENTRIUS_VERSION=1.1.334
SENTRIUS_SSH_VERSION=1.1.41
SENTRIUS_KEYCLOAK_VERSION=1.1.53
SENTRIUS_AGENT_VERSION=1.1.42
SENTRIUS_AI_AGENT_VERSION=1.1.263
LLMPROXY_VERSION=1.0.78
LAUNCHER_VERSION=1.0.82
AGENTPROXY_VERSION=1.0.75
AGENTPROXY_VERSION=1.0.85
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ private ReactiveJwtAuthenticationConverter grantedAuthoritiesExtractor() {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
log.info("Configuring CORS for agent API URL: {}", agentApiUrl);
config.setAllowedOrigins(List.of(agentApiUrl));
config.setAllowedMethods(List.of("GET", "POST", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.sentrius.sso.controller;

import java.util.List;
import java.util.Map;
import io.sentrius.sso.core.dto.TerminalLogDTO;
import io.sentrius.sso.service.ActiveWebSocketSessionManager;
import org.springframework.http.HttpHeaders;
Expand All @@ -27,4 +28,14 @@ public List<TerminalLogDTO> listSessions() {
return activeWebSocketSessionManager.getActiveSessions();
}

@GetMapping("/agent/durations")
public List<Map<String, Object>> getAgentSessionDurations() {
return activeWebSocketSessionManager.getAgentSessionDurations();
}

@GetMapping("/agent/active-durations")
public List<Map<String, Object>> getActiveAgentSessionDurations() {
return activeWebSocketSessionManager.getActiveAgentSessionDurations();
}

}
Original file line number Diff line number Diff line change
@@ -1,25 +1,55 @@
package io.sentrius.sso.service;

import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import io.sentrius.sso.core.dto.TerminalLogDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.socket.WebSocketSession;

@Slf4j
@Component
public class ActiveWebSocketSessionManager {
private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
private final Map<String, Timestamp> sessionStartTimes = new ConcurrentHashMap<>();
private final List<Map<String, Object>> completedAgentSessions = new ArrayList<>();

public void register(String sessionId, WebSocketSession session) {
sessions.put(sessionId, session);
sessionStartTimes.put(sessionId, new Timestamp(System.currentTimeMillis()));
}

public void unregister(String sessionId) {
sessions.remove(sessionId);
WebSocketSession session = sessions.remove(sessionId);
Timestamp startTime = sessionStartTimes.remove(sessionId);

if (startTime != null) {
// Calculate duration and store completed session
Timestamp endTime = new Timestamp(System.currentTimeMillis());
long durationMinutes = ChronoUnit.MINUTES.between(
startTime.toLocalDateTime(),
endTime.toLocalDateTime()
);

Map<String, Object> completedSession = new HashMap<>();
completedSession.put("sessionId", sessionId);
completedSession.put("startTime", startTime);
completedSession.put("endTime", endTime);
completedSession.put("durationMinutes", durationMinutes);
completedSession.put("sessionType", "agent");

synchronized (completedAgentSessions) {
completedAgentSessions.add(completedSession);
}
}
}

public WebSocketSession get(String sessionId) {
Expand All @@ -37,4 +67,46 @@ public List<TerminalLogDTO> getActiveSessions() {
.build())
.collect(Collectors.toList());
}

/**
* Get session duration data for agent sessions
* @return List of session duration data
*/
public List<Map<String, Object>> getAgentSessionDurations() {
synchronized (completedAgentSessions) {
log.info("Returning {} completed agent sessions", completedAgentSessions.size());
return new ArrayList<>(completedAgentSessions);
}
}

/**
* Get current active agent session durations (for sessions still in progress)
* @return List of active session duration data
*/
public List<Map<String, Object>> getActiveAgentSessionDurations() {
List<Map<String, Object>> activeDurations = new ArrayList<>();

for (Map.Entry<String, Timestamp> entry : sessionStartTimes.entrySet()) {
String sessionId = entry.getKey();
Timestamp startTime = entry.getValue();

if (sessions.containsKey(sessionId)) {
long durationMinutes = ChronoUnit.MINUTES.between(
startTime.toLocalDateTime(),
LocalDateTime.now()
);

Map<String, Object> activeSession = new HashMap<>();
activeSession.put("sessionId", sessionId);
activeSession.put("startTime", startTime);
activeSession.put("durationMinutes", durationMinutes);
activeSession.put("sessionType", "agent");
activeSession.put("active", true);

activeDurations.add(activeSession);
}
}
log.info("Returning {} active agent session durations", activeDurations.size());
return activeDurations;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package io.sentrius.sso.service;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.reactive.socket.WebSocketSession;
import org.springframework.web.reactive.socket.HandshakeInfo;

import java.net.InetSocketAddress;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class ActiveWebSocketSessionManagerTest {

@Mock
private WebSocketSession webSocketSession;

@Mock
private HandshakeInfo handshakeInfo;

private ActiveWebSocketSessionManager sessionManager;

@BeforeEach
void setUp() {
sessionManager = new ActiveWebSocketSessionManager();
}

@Test
void testRegisterAndUnregisterSession() {
// Given
String sessionId = "test-session-1";
when(webSocketSession.getId()).thenReturn(sessionId);
when(webSocketSession.isOpen()).thenReturn(true);
when(webSocketSession.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 8080));

// When - register session
sessionManager.register(sessionId, webSocketSession);

// Then - session should be active
assertEquals(webSocketSession, sessionManager.get(sessionId));
assertEquals(1, sessionManager.getActiveSessions().size());
assertEquals(0, sessionManager.getAgentSessionDurations().size());

// When - unregister session
sessionManager.unregister(sessionId);

// Then - session should be removed and duration recorded
assertNull(sessionManager.get(sessionId));
assertEquals(0, sessionManager.getActiveSessions().size());
assertEquals(1, sessionManager.getAgentSessionDurations().size());

// Verify session duration data
List<Map<String, Object>> completedSessions = sessionManager.getAgentSessionDurations();
Map<String, Object> sessionData = completedSessions.get(0);
assertEquals(sessionId, sessionData.get("sessionId"));
assertEquals("agent", sessionData.get("sessionType"));
assertNotNull(sessionData.get("startTime"));
assertNotNull(sessionData.get("endTime"));
assertNotNull(sessionData.get("durationMinutes"));
assertTrue((Long) sessionData.get("durationMinutes") >= 0);
}

@Test
void testGetActiveAgentSessionDurations() throws InterruptedException {
// Given
String sessionId = "active-session-1";

// When
sessionManager.register(sessionId, webSocketSession);

// Wait a moment to ensure some time passes
Thread.sleep(100);

// Then
List<Map<String, Object>> activeSessions = sessionManager.getActiveAgentSessionDurations();
assertEquals(1, activeSessions.size());

Map<String, Object> activeSession = activeSessions.get(0);
assertEquals(sessionId, activeSession.get("sessionId"));
assertEquals("agent", activeSession.get("sessionType"));
assertEquals(true, activeSession.get("active"));
assertNotNull(activeSession.get("startTime"));
assertNotNull(activeSession.get("durationMinutes"));
assertTrue((Long) activeSession.get("durationMinutes") >= 0);
}

@Test
void testMultipleSessionsHandling() {
// Given
String sessionId1 = "session-1";
String sessionId2 = "session-2";
WebSocketSession session1 = mock(WebSocketSession.class);
WebSocketSession session2 = mock(WebSocketSession.class);

when(session1.getId()).thenReturn(sessionId1);
when(session1.isOpen()).thenReturn(true);
when(session1.getHandshakeInfo()).thenReturn(handshakeInfo);
when(session2.getId()).thenReturn(sessionId2);
when(session2.isOpen()).thenReturn(true);
when(session2.getHandshakeInfo()).thenReturn(handshakeInfo);
when(handshakeInfo.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 8080));

// When
sessionManager.register(sessionId1, session1);
sessionManager.register(sessionId2, session2);

// Then
assertEquals(2, sessionManager.getActiveSessions().size());
assertEquals(2, sessionManager.getActiveAgentSessionDurations().size());

// When - unregister one session
sessionManager.unregister(sessionId1);

// Then
assertEquals(1, sessionManager.getActiveSessions().size());
assertEquals(1, sessionManager.getActiveAgentSessionDurations().size());
assertEquals(1, sessionManager.getAgentSessionDurations().size());
}

@Test
void testUnregisterNonExistentSession() {
// Given
String nonExistentSessionId = "non-existent";

// When
sessionManager.unregister(nonExistentSessionId);

// Then - should not throw exception and should not affect other data
assertEquals(0, sessionManager.getActiveSessions().size());
assertEquals(0, sessionManager.getAgentSessionDurations().size());
assertEquals(0, sessionManager.getActiveAgentSessionDurations().size());
}
}
Loading