소스 검색

GUI tab players

dagarcos 1 개월 전
부모
커밋
b7fb7d7bb0

+ 74 - 2
GUI/index.html

@@ -133,8 +133,17 @@
         </div>
         <div class="col-md-6">
           <h6>Activity Snapshot</h6>
-          <div class="alert alert-light">
-            <canvas id="snapshotChart"></canvas>
+          <div class="alert alert-light">  
+            <div class="row">
+              <div class="col-md-6">
+                <h6>Events over time (Snapshots)</h6>
+                <canvas id="snapshotChart"></canvas>
+              </div>
+              <div class="col-md-6">
+                <h6>Events over time (Events)</h6>
+                <canvas id="eventsChart"></canvas>
+              </div>
+            </div>
           </div>
         </div>
       </div>
@@ -250,6 +259,7 @@
 
     let selectedShardId = null;
     let snapshotChart = null;
+    let eventsChart = null;
 
     async function refreshShardsTab() {
         try {
@@ -260,6 +270,7 @@
         }
     }
 
+    /* FUNCIONES PARA ACTUALIZAR LA PESTAÑA SHARDS */
     async function loadDashboardSummary() {
         const response = await fetch(`${API_BASE}/master/dashboard/summary`);
         const summary = await response.json();
@@ -300,6 +311,7 @@
 
             if (shard.shardId === selectedShardId) {
                 tr.classList.add("shard-selected");
+                loadShardDetail();
             }
 
             tr.addEventListener("click", () => {
@@ -371,9 +383,62 @@
         );
     }
 
+    async function loadShardEventsChart() {
+        if (!selectedShardId) return;
+
+        const res = await fetch(
+            `${API_BASE}/master/shards/${selectedShardId}/events/timeseries`
+        );
+        const series = await res.json();
+
+        const labels = series.map(p =>
+            new Date(p.timestamp).toLocaleTimeString()
+        );
+        const data = series.map(p => p.totalEvents);
+
+        if (eventsChart) eventsChart.destroy();
+
+        eventsChart = new Chart(
+            document.getElementById("eventsChart"),
+            {
+                type: "line",
+                data: {
+                labels,
+                datasets: [{
+                    label: "Total Events (Events table)",
+                    data,
+                    borderColor: "red",
+                    tension: 0.2
+                }]
+                }
+            }
+        );
+    }
+
     async function loadShardDetail() {
         await loadShardPlayers();
         await loadShardSnapshots();
+        await loadShardEventsChart();
+    }
+
+    /* FUNCIONES PARA ACTUALIZAR LA PESTAÑA PLAYERS */
+    async function loadPlayersGlobal() {
+        const res = await fetch(`${API_BASE}/master/players`);
+        const players = await res.json();
+
+        const tbody = document.getElementById("tablePlayers");
+        tbody.innerHTML = "";
+
+        players.forEach(p => {
+            const tr = document.createElement("tr");
+            tr.innerHTML = `
+                <td>${p.playerId}</td>
+                <td>${p.globalScore}</td>
+                <td>${p.shardsPlayed}</td>
+                <td>${p.totalActions}</td>
+                `;
+            tbody.appendChild(tr);
+        });
     }
 
 
@@ -408,6 +473,13 @@
     // Refresco automático
     setInterval(refreshShardsTab, REFRESH_INTERVAL_MS);
 
+
+    document.querySelector('button[data-bs-target="#tabPlayers"]')
+            .addEventListener("shown.bs.tab", () => {
+                loadPlayersGlobal();
+            });
+
+
 </script>
 
 </body>

+ 8 - 0
src/main/java/es/uv/dagarcos/master/controller/DashboardController.java

@@ -1,6 +1,7 @@
 package es.uv.dagarcos.master.controller;
 
 import es.uv.dagarcos.master.dto.DashboardSummaryDto;
+import es.uv.dagarcos.master.dto.ShardEventTimeSeriesDto;
 import es.uv.dagarcos.master.dto.ShardOverviewDto;
 import es.uv.dagarcos.master.dto.ShardPlayerStatsDto;
 import es.uv.dagarcos.master.dto.ShardWorldSnapshotDto;
@@ -45,4 +46,11 @@ public class DashboardController {
         return shardWorldSnapshotService.getSnapshots(shardId);
     }
 
+
+    @GetMapping("/shards/{shardId}/events/timeseries")
+    public List<ShardEventTimeSeriesDto> getShardEventTimeSeries(@PathVariable String shardId) {
+        return dashboardService.getEventTimeSeries(shardId);
+    }
+
+
 }

+ 24 - 0
src/main/java/es/uv/dagarcos/master/controller/PlayerDashboardController.java

@@ -0,0 +1,24 @@
+package es.uv.dagarcos.master.controller;
+
+import es.uv.dagarcos.master.dto.PlayerOverviewDto;
+import es.uv.dagarcos.master.service.PlayerDashboardService;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/master/players")
+@CrossOrigin
+public class PlayerDashboardController {
+
+    @Autowired
+    private PlayerDashboardService service;
+
+    @GetMapping
+    public List<PlayerOverviewDto> getPlayers() {
+        return service.getGlobalPlayers();
+    }
+
+}

+ 55 - 0
src/main/java/es/uv/dagarcos/master/dto/PlayerOverviewDto.java

@@ -0,0 +1,55 @@
+package es.uv.dagarcos.master.dto;
+
+public class PlayerOverviewDto {
+
+    private String playerId;
+    private long globalScore;
+    private long shardsPlayed;
+    private long totalActions;
+
+    public PlayerOverviewDto(
+            String playerId,
+            long globalScore,
+            long shardsPlayed,
+            long totalActions) {
+
+        this.playerId = playerId;
+        this.globalScore = globalScore;
+        this.shardsPlayed = shardsPlayed;
+        this.totalActions = totalActions;
+    }
+
+    public String getPlayerId() {
+        return playerId;
+    }
+
+    public void setPlayerId(String playerId) {
+        this.playerId = playerId;
+    }
+
+    public long getGlobalScore() {
+        return globalScore;
+    }
+
+    public void setGlobalScore(long globalScore) {
+        this.globalScore = globalScore;
+    }
+
+    public long getShardsPlayed() {
+        return shardsPlayed;
+    }
+
+    public void setShardsPlayed(long shardsPlayed) {
+        this.shardsPlayed = shardsPlayed;
+    }
+
+    public long getTotalActions() {
+        return totalActions;
+    }
+
+    public void setTotalActions(long totalActions) {
+        this.totalActions = totalActions;
+    } 
+    
+}
+

+ 32 - 0
src/main/java/es/uv/dagarcos/master/dto/ShardEventTimeSeriesDto.java

@@ -0,0 +1,32 @@
+package es.uv.dagarcos.master.dto;
+
+import java.time.Instant;
+
+public class ShardEventTimeSeriesDto {
+
+    private Instant timestamp;
+    private long totalEvents;
+
+    public ShardEventTimeSeriesDto(Instant timestamp, long totalEvents) {
+        this.timestamp = timestamp;
+        this.totalEvents = totalEvents;
+    }
+
+    public Instant getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(Instant timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public long getTotalEvents() {
+        return totalEvents;
+    }
+
+    public void setTotalEvents(long totalEvents) {
+        this.totalEvents = totalEvents;
+    }
+
+    
+}

+ 12 - 0
src/main/java/es/uv/dagarcos/master/repository/MasterEventRepository.java

@@ -5,6 +5,7 @@ import es.uv.dagarcos.master.domain.PlayerGlobal;
 import es.uv.dagarcos.master.domain.Shard;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
 
 import java.time.Instant;
 import java.util.List;
@@ -32,4 +33,15 @@ public interface MasterEventRepository extends JpaRepository<MasterEvent, Long>
     """)
     List<Object[]> countEventsPerShard();
 
+    @Query("""
+    SELECT 
+        FUNCTION('date_trunc', 'minute', e.timestamp),
+        COUNT(e)
+    FROM MasterEvent e
+    WHERE e.shard.externalId = :shardId
+    GROUP BY FUNCTION('date_trunc', 'minute', e.timestamp)
+    ORDER BY FUNCTION('date_trunc', 'minute', e.timestamp)
+    """)
+    List<Object[]> countEventsTimeSeries(@Param("shardId") String shardId);
+
 }

+ 11 - 0
src/main/java/es/uv/dagarcos/master/repository/ShardPlayerStatsRepository.java

@@ -33,4 +33,15 @@ public interface ShardPlayerStatsRepository extends JpaRepository<ShardPlayerSta
     """)
     List<ShardPlayerStats> findByShardOrdered(@Param("shardId") String shardId);
 
+    @Query("""
+        SELECT 
+            sps.player.externalPlayerId,
+            COUNT(sps.shard.id),
+            SUM(sps.totalActions),
+            SUM(sps.score)
+        FROM ShardPlayerStats sps
+        GROUP BY sps.player.id
+    """)
+    List<Object[]> aggregateGlobalPlayerStats();
+
 }

+ 30 - 0
src/main/java/es/uv/dagarcos/master/service/PlayerDashboardService.java

@@ -0,0 +1,30 @@
+package es.uv.dagarcos.master.service;
+
+import es.uv.dagarcos.master.dto.PlayerOverviewDto;
+import es.uv.dagarcos.master.repository.ShardPlayerStatsRepository;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class PlayerDashboardService {
+
+    @Autowired
+    private ShardPlayerStatsRepository statsRepository;
+
+    public List<PlayerOverviewDto> getGlobalPlayers() {
+
+        return statsRepository.aggregateGlobalPlayerStats()
+                .stream()
+                .map(row -> new PlayerOverviewDto(
+                        (String) row[0],
+                        (Long) row[3],
+                        (Long) row[1],
+                        (Long) row[2]
+                ))
+                .toList();
+    }
+}
+

+ 13 - 0
src/main/java/es/uv/dagarcos/master/service/ShardDashboardService.java

@@ -1,6 +1,7 @@
 package es.uv.dagarcos.master.service;
 
 import es.uv.dagarcos.master.dto.DashboardSummaryDto;
+import es.uv.dagarcos.master.dto.ShardEventTimeSeriesDto;
 import es.uv.dagarcos.master.dto.ShardOverviewDto;
 import es.uv.dagarcos.master.domain.Shard;
 import es.uv.dagarcos.master.repository.*;
@@ -27,6 +28,7 @@ public class ShardDashboardService {
     @Autowired
     private PlayerGlobalRepository playerRepository;
 
+
     public List<ShardOverviewDto> getShardOverview() {
 
         Map<Long, Long> eventsByShard = new HashMap<>();
@@ -63,4 +65,15 @@ public class ShardDashboardService {
                 shardRepository.countActiveShards(activeThreshold)
         );
     }
+
+    public List<ShardEventTimeSeriesDto> getEventTimeSeries(String shardId) {
+            return eventRepository
+                    .countEventsTimeSeries(shardId)
+                    .stream()
+                    .map(row -> new ShardEventTimeSeriesDto(
+                            (Instant) row[0],
+                            (Long) row[1]
+                    ))
+                    .toList();
+        }
 }