@zumito-team/analytics-module 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/translations/en.json +63 -0
- package/dist/translations/es.json +63 -0
- package/dist/views/admin-analytics.ejs +170 -0
- package/dist/views/user-analytics.ejs +209 -0
- package/package.json +4 -4
- package/tsconfig.json +1 -1
- /package/{commands → dist/commands}/AnalyticsConfig.d.ts +0 -0
- /package/{commands → dist/commands}/AnalyticsConfig.js +0 -0
- /package/{commands → dist/commands}/Stats.d.ts +0 -0
- /package/{commands → dist/commands}/Stats.js +0 -0
- /package/{config.d.ts → dist/config.d.ts} +0 -0
- /package/{config.js → dist/config.js} +0 -0
- /package/{events → dist/events}/discord/GuildMemberAdd.d.ts +0 -0
- /package/{events → dist/events}/discord/GuildMemberAdd.js +0 -0
- /package/{events → dist/events}/discord/GuildMemberRemove.d.ts +0 -0
- /package/{events → dist/events}/discord/GuildMemberRemove.js +0 -0
- /package/{events → dist/events}/discord/MessageCreate.d.ts +0 -0
- /package/{events → dist/events}/discord/MessageCreate.js +0 -0
- /package/{events → dist/events}/discord/VoiceStateUpdate.d.ts +0 -0
- /package/{events → dist/events}/discord/VoiceStateUpdate.js +0 -0
- /package/{events → dist/events}/framework/CommandExecuted.d.ts +0 -0
- /package/{events → dist/events}/framework/CommandExecuted.js +0 -0
- /package/{index.d.ts → dist/index.d.ts} +0 -0
- /package/{index.js → dist/index.js} +0 -0
- /package/{models → dist/models}/CommandDailyStats.d.ts +0 -0
- /package/{models → dist/models}/CommandDailyStats.js +0 -0
- /package/{models → dist/models}/GuildAnalyticsConfig.d.ts +0 -0
- /package/{models → dist/models}/GuildAnalyticsConfig.js +0 -0
- /package/{models → dist/models}/GuildDailyStats.d.ts +0 -0
- /package/{models → dist/models}/GuildDailyStats.js +0 -0
- /package/{models → dist/models}/VoiceChannelDailyStats.d.ts +0 -0
- /package/{models → dist/models}/VoiceChannelDailyStats.js +0 -0
- /package/{routes → dist/routes}/AdminAnalytics.d.ts +0 -0
- /package/{routes → dist/routes}/AdminAnalytics.js +0 -0
- /package/{routes → dist/routes}/UserPanelAnalytics.d.ts +0 -0
- /package/{routes → dist/routes}/UserPanelAnalytics.js +0 -0
- /package/{services → dist/services}/AnalyticsCollector.d.ts +0 -0
- /package/{services → dist/services}/AnalyticsCollector.js +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.stats.title": "Server Stats",
|
|
3
|
+
"command.stats.description": "Last {days} days summary",
|
|
4
|
+
"command.stats.messages": "Messages",
|
|
5
|
+
"command.stats.commands": "Commands",
|
|
6
|
+
"command.stats.joins": "Joins",
|
|
7
|
+
"command.stats.leaves": "Leaves",
|
|
8
|
+
"command.stats.voice": "Voice Activity",
|
|
9
|
+
"command.stats.noGuild": "This command can only be used in a server.",
|
|
10
|
+
"command.analytics-config.title": "Analytics Configuration",
|
|
11
|
+
"command.analytics-config.configTitle": "Analytics Configuration",
|
|
12
|
+
"command.analytics-config.configDescription": "Current analytics tracking settings for this server.",
|
|
13
|
+
"command.analytics-config.configUpdated": "Configuration Updated",
|
|
14
|
+
"command.analytics-config.enabled": "Enabled",
|
|
15
|
+
"command.analytics-config.trackMessages": "Track Messages",
|
|
16
|
+
"command.analytics-config.trackVoice": "Track Voice",
|
|
17
|
+
"command.analytics-config.trackMembers": "Track Members",
|
|
18
|
+
"command.analytics-config.trackCommands": "Track Commands",
|
|
19
|
+
"command.analytics-config.trackPerformance": "Track Command Performance",
|
|
20
|
+
"command.analytics-config.trackPerChannelVoice": "Track Per-Channel Voice",
|
|
21
|
+
"command.analytics-config.retentionDays": "Retention (days)",
|
|
22
|
+
"command.analytics-config.publicStats": "Public Stats Page",
|
|
23
|
+
"command.analytics-config.noGuild": "This command can only be used in a server.",
|
|
24
|
+
"command.analytics-config.noPermission": "You need administrator permissions to manage analytics.",
|
|
25
|
+
"analytics.sidebarTitle": "Analytics",
|
|
26
|
+
"analytics.title": "Analytics",
|
|
27
|
+
"analytics.botStats": "Bot Statistics",
|
|
28
|
+
"analytics.serverStats": "Server Statistics",
|
|
29
|
+
"analytics.messages": "Messages",
|
|
30
|
+
"analytics.commands": "Commands",
|
|
31
|
+
"analytics.joins": "Joins",
|
|
32
|
+
"analytics.leaves": "Leaves",
|
|
33
|
+
"analytics.voice": "Voice Activity",
|
|
34
|
+
"analytics.errors": "Errors",
|
|
35
|
+
"analytics.totalGuilds": "Total Servers",
|
|
36
|
+
"analytics.activeGuilds": "Active Servers",
|
|
37
|
+
"analytics.totalMembers": "Members Reach",
|
|
38
|
+
"analytics.messagesPerDay": "Messages per Day",
|
|
39
|
+
"analytics.commandsPerDay": "Commands per Day",
|
|
40
|
+
"analytics.joinsVsLeaves": "Joins vs Leaves",
|
|
41
|
+
"analytics.voiceActivity": "Voice Activity",
|
|
42
|
+
"analytics.topCommands": "Top Commands",
|
|
43
|
+
"analytics.slowestCommands": "Slowest Commands",
|
|
44
|
+
"analytics.guildGrowth": "Server Growth",
|
|
45
|
+
"analytics.last7days": "Last 7 Days",
|
|
46
|
+
"analytics.last30days": "Last 30 Days",
|
|
47
|
+
"analytics.last90days": "Last 90 Days",
|
|
48
|
+
"analytics.noData": "No data available",
|
|
49
|
+
"analytics.commandName": "Command",
|
|
50
|
+
"analytics.usageCount": "Uses",
|
|
51
|
+
"analytics.avgExecutionTime": "Avg. Time",
|
|
52
|
+
"analytics.executionTime": "Execution Time",
|
|
53
|
+
"analytics.overview": "Overview",
|
|
54
|
+
"analytics.detailedStats": "Detailed Stats",
|
|
55
|
+
"analytics.minutes": "minutes",
|
|
56
|
+
"analytics.globalStats": "Global",
|
|
57
|
+
"analytics.perChannelVoice": "Voice per Channel",
|
|
58
|
+
"analytics.channelName": "Channel",
|
|
59
|
+
"analytics.totalMinutes": "Total Minutes",
|
|
60
|
+
"analytics.uniqueUsers": "Unique Users",
|
|
61
|
+
"analytics.date": "Date",
|
|
62
|
+
"analytics.count": "Count"
|
|
63
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"command.stats.title": "Estadisticas del Servidor",
|
|
3
|
+
"command.stats.description": "Resumen de los ultimos {days} dias",
|
|
4
|
+
"command.stats.messages": "Mensajes",
|
|
5
|
+
"command.stats.commands": "Comandos",
|
|
6
|
+
"command.stats.joins": "Entradas",
|
|
7
|
+
"command.stats.leaves": "Salidas",
|
|
8
|
+
"command.stats.voice": "Actividad de Voz",
|
|
9
|
+
"command.stats.noGuild": "Este comando solo puede usarse en un servidor.",
|
|
10
|
+
"command.analytics-config.title": "Configuracion de Analiticas",
|
|
11
|
+
"command.analytics-config.configTitle": "Configuracion de Analiticas",
|
|
12
|
+
"command.analytics-config.configDescription": "Ajustes actuales de seguimiento de analiticas para este servidor.",
|
|
13
|
+
"command.analytics-config.configUpdated": "Configuracion Actualizada",
|
|
14
|
+
"command.analytics-config.enabled": "Activado",
|
|
15
|
+
"command.analytics-config.trackMessages": "Seguimiento de Mensajes",
|
|
16
|
+
"command.analytics-config.trackVoice": "Seguimiento de Voz",
|
|
17
|
+
"command.analytics-config.trackMembers": "Seguimiento de Miembros",
|
|
18
|
+
"command.analytics-config.trackCommands": "Seguimiento de Comandos",
|
|
19
|
+
"command.analytics-config.trackPerformance": "Seguimiento de Rendimiento",
|
|
20
|
+
"command.analytics-config.trackPerChannelVoice": "Voz por Canal",
|
|
21
|
+
"command.analytics-config.retentionDays": "Retencion (dias)",
|
|
22
|
+
"command.analytics-config.publicStats": "Pagina de Estadisticas Publica",
|
|
23
|
+
"command.analytics-config.noGuild": "Este comando solo puede usarse en un servidor.",
|
|
24
|
+
"command.analytics-config.noPermission": "Necesitas permisos de administrador para gestionar las analiticas.",
|
|
25
|
+
"analytics.sidebarTitle": "Analiticas",
|
|
26
|
+
"analytics.title": "Analiticas",
|
|
27
|
+
"analytics.botStats": "Estadisticas del Bot",
|
|
28
|
+
"analytics.serverStats": "Estadisticas del Servidor",
|
|
29
|
+
"analytics.messages": "Mensajes",
|
|
30
|
+
"analytics.commands": "Comandos",
|
|
31
|
+
"analytics.joins": "Entradas",
|
|
32
|
+
"analytics.leaves": "Salidas",
|
|
33
|
+
"analytics.voice": "Actividad de Voz",
|
|
34
|
+
"analytics.errors": "Errores",
|
|
35
|
+
"analytics.totalGuilds": "Servidores Totales",
|
|
36
|
+
"analytics.activeGuilds": "Servidores Activos",
|
|
37
|
+
"analytics.totalMembers": "Alcance de Miembros",
|
|
38
|
+
"analytics.messagesPerDay": "Mensajes por Dia",
|
|
39
|
+
"analytics.commandsPerDay": "Comandos por Dia",
|
|
40
|
+
"analytics.joinsVsLeaves": "Entradas vs Salidas",
|
|
41
|
+
"analytics.voiceActivity": "Actividad de Voz",
|
|
42
|
+
"analytics.topCommands": "Comandos Mas Usados",
|
|
43
|
+
"analytics.slowestCommands": "Comandos Mas Lentos",
|
|
44
|
+
"analytics.guildGrowth": "Crecimiento de Servidores",
|
|
45
|
+
"analytics.last7days": "Ultimos 7 Dias",
|
|
46
|
+
"analytics.last30days": "Ultimos 30 Dias",
|
|
47
|
+
"analytics.last90days": "Ultimos 90 Dias",
|
|
48
|
+
"analytics.noData": "Sin datos disponibles",
|
|
49
|
+
"analytics.commandName": "Comando",
|
|
50
|
+
"analytics.usageCount": "Usos",
|
|
51
|
+
"analytics.avgExecutionTime": "Tiempo Medio",
|
|
52
|
+
"analytics.executionTime": "Tiempo de Ejecucion",
|
|
53
|
+
"analytics.overview": "Resumen",
|
|
54
|
+
"analytics.detailedStats": "Estadisticas Detalladas",
|
|
55
|
+
"analytics.minutes": "minutos",
|
|
56
|
+
"analytics.globalStats": "Global",
|
|
57
|
+
"analytics.perChannelVoice": "Voz por Canal",
|
|
58
|
+
"analytics.channelName": "Canal",
|
|
59
|
+
"analytics.totalMinutes": "Minutos Totales",
|
|
60
|
+
"analytics.uniqueUsers": "Usuarios Unicos",
|
|
61
|
+
"analytics.date": "Fecha",
|
|
62
|
+
"analytics.count": "Cantidad"
|
|
63
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<div class="p-4">
|
|
2
|
+
<div class="flex items-center justify-between mb-6">
|
|
3
|
+
<h1 class="text-2xl font-bold text-discord-foreground"><%= t('analytics.botStats') %></h1>
|
|
4
|
+
<div class="flex gap-2">
|
|
5
|
+
<button data-days="7" class="date-btn btn-primary text-sm px-3 py-1 rounded"><%= t('analytics.last7days') %></button>
|
|
6
|
+
<button data-days="30" class="date-btn text-sm px-3 py-1 rounded bg-discord-dark-100 text-discord-foreground/70 hover:text-discord-foreground"><%= t('analytics.last30days') %></button>
|
|
7
|
+
<button data-days="90" class="date-btn text-sm px-3 py-1 rounded bg-discord-dark-100 text-discord-foreground/70 hover:text-discord-foreground"><%= t('analytics.last90days') %></button>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
|
|
12
|
+
<div class="card text-center">
|
|
13
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= summary.totalGuilds %></div>
|
|
14
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.totalGuilds') %></div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="card text-center">
|
|
17
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= summary.totalMessages %></div>
|
|
18
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.messages') %></div>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="card text-center">
|
|
21
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= summary.totalCommands %></div>
|
|
22
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.commands') %></div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="card text-center">
|
|
25
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= summary.totalJoins %></div>
|
|
26
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.joins') %></div>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="card text-center">
|
|
29
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= Math.round(summary.totalVoiceMinutes) %></div>
|
|
30
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.voice') %> (min)</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
35
|
+
<div class="card">
|
|
36
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.guildGrowth') %></h3>
|
|
37
|
+
<canvas id="chartGuildGrowth" width="400" height="200"></canvas>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="card">
|
|
40
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.messagesPerDay') %></h3>
|
|
41
|
+
<canvas id="chartMessages" width="400" height="200"></canvas>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="card">
|
|
44
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.commandsPerDay') %></h3>
|
|
45
|
+
<canvas id="chartCommands" width="400" height="200"></canvas>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="card">
|
|
48
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.topCommands') %></h3>
|
|
49
|
+
<canvas id="chartTopCommands" width="400" height="200"></canvas>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<% if (typeof slowestCommands !== 'undefined' && slowestCommands.length > 0) { %>
|
|
54
|
+
<div class="card mb-6">
|
|
55
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.slowestCommands') %></h3>
|
|
56
|
+
<canvas id="chartSlowestCommands" width="600" height="200"></canvas>
|
|
57
|
+
</div>
|
|
58
|
+
<% } %>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
62
|
+
<script>
|
|
63
|
+
(function() {
|
|
64
|
+
const colors = {
|
|
65
|
+
primary: '#5865f2',
|
|
66
|
+
success: '#57f287',
|
|
67
|
+
warning: '#fee75c',
|
|
68
|
+
danger: '#ed4245',
|
|
69
|
+
foreground: '#ffffff',
|
|
70
|
+
foreground60: 'rgba(255,255,255,0.6)',
|
|
71
|
+
dark100: '#36393f',
|
|
72
|
+
dark200: '#2f3136',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
Chart.defaults.color = colors.foreground60;
|
|
76
|
+
Chart.defaults.borderColor = colors.dark100;
|
|
77
|
+
Chart.defaults.plugins.legend.labels.color = colors.foreground60;
|
|
78
|
+
|
|
79
|
+
const data = <%- JSON.stringify(chartData) %>;
|
|
80
|
+
|
|
81
|
+
function createLineChart(canvasId, label, values, keyX, keyY) {
|
|
82
|
+
new Chart(document.getElementById(canvasId), {
|
|
83
|
+
type: 'line',
|
|
84
|
+
data: {
|
|
85
|
+
labels: values.map(v => v[keyX]),
|
|
86
|
+
datasets: [{
|
|
87
|
+
label: label,
|
|
88
|
+
data: values.map(v => v[keyY]),
|
|
89
|
+
borderColor: colors.primary,
|
|
90
|
+
backgroundColor: 'rgba(88,101,242,0.1)',
|
|
91
|
+
fill: true,
|
|
92
|
+
tension: 0.3,
|
|
93
|
+
}]
|
|
94
|
+
},
|
|
95
|
+
options: { responsive: true, plugins: { legend: { display: false } } }
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function createBarChart(canvasId, label, values, keyX, keyY) {
|
|
100
|
+
new Chart(document.getElementById(canvasId), {
|
|
101
|
+
type: 'bar',
|
|
102
|
+
data: {
|
|
103
|
+
labels: values.map(v => v[keyX]),
|
|
104
|
+
datasets: [{
|
|
105
|
+
label: label,
|
|
106
|
+
data: values.map(v => v[keyY]),
|
|
107
|
+
backgroundColor: colors.primary,
|
|
108
|
+
borderRadius: 4,
|
|
109
|
+
}]
|
|
110
|
+
},
|
|
111
|
+
options: { responsive: true, plugins: { legend: { display: false } } }
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function createHorizontalBarChart(canvasId, label, values, keyX, keyY) {
|
|
116
|
+
new Chart(document.getElementById(canvasId), {
|
|
117
|
+
type: 'bar',
|
|
118
|
+
data: {
|
|
119
|
+
labels: values.map(v => v[keyX]),
|
|
120
|
+
datasets: [{
|
|
121
|
+
label: label,
|
|
122
|
+
data: values.map(v => v[keyY]),
|
|
123
|
+
backgroundColor: values.map((_, i) => {
|
|
124
|
+
const alpha = 1 - (i * 0.08);
|
|
125
|
+
return `rgba(88,101,242,${Math.max(0.3, alpha)})`;
|
|
126
|
+
}),
|
|
127
|
+
borderRadius: 4,
|
|
128
|
+
}]
|
|
129
|
+
},
|
|
130
|
+
options: {
|
|
131
|
+
indexAxis: 'y',
|
|
132
|
+
responsive: true,
|
|
133
|
+
plugins: { legend: { display: false } }
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
createLineChart('chartGuildGrowth', '<%= t("analytics.guildGrowth") %>', data.guildGrowth, 'date', 'guildCount');
|
|
139
|
+
createBarChart('chartMessages', '<%= t("analytics.messagesPerDay") %>', data.messagesPerDay, 'date', 'count');
|
|
140
|
+
createBarChart('chartCommands', '<%= t("analytics.commandsPerDay") %>', data.commandsPerDay, 'date', 'count');
|
|
141
|
+
createHorizontalBarChart('chartTopCommands', '<%= t("analytics.topCommands") %>', data.topCommands, 'command_name', 'usage_count');
|
|
142
|
+
|
|
143
|
+
<% if (typeof slowestCommands !== 'undefined' && slowestCommands.length > 0) { %>
|
|
144
|
+
new Chart(document.getElementById('chartSlowestCommands'), {
|
|
145
|
+
type: 'bar',
|
|
146
|
+
data: {
|
|
147
|
+
labels: data.slowestCommands.map(v => v.command_name),
|
|
148
|
+
datasets: [{
|
|
149
|
+
label: '<%= t("analytics.avgExecutionTime") %> (ms)',
|
|
150
|
+
data: data.slowestCommands.map(v => v.avgExecutionTimeMs),
|
|
151
|
+
backgroundColor: colors.warning,
|
|
152
|
+
borderRadius: 4,
|
|
153
|
+
}]
|
|
154
|
+
},
|
|
155
|
+
options: {
|
|
156
|
+
responsive: true,
|
|
157
|
+
plugins: { legend: { display: false } },
|
|
158
|
+
scales: { y: { title: { display: true, text: 'ms' } } }
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
<% } %>
|
|
162
|
+
|
|
163
|
+
document.querySelectorAll('.date-btn').forEach(btn => {
|
|
164
|
+
btn.addEventListener('click', () => {
|
|
165
|
+
const days = btn.dataset.days;
|
|
166
|
+
window.location.href = '/admin/analytics?days=' + days;
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
})();
|
|
170
|
+
</script>
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<div class="p-4">
|
|
2
|
+
<div class="flex items-center justify-between mb-6">
|
|
3
|
+
<h1 class="text-2xl font-bold text-discord-foreground"><%= t('analytics.serverStats') %></h1>
|
|
4
|
+
<div class="flex gap-2">
|
|
5
|
+
<button data-days="7" class="date-btn btn-primary text-sm px-3 py-1 rounded"><%= t('analytics.last7days') %></button>
|
|
6
|
+
<button data-days="30" class="date-btn text-sm px-3 py-1 rounded bg-discord-dark-100 text-discord-foreground/70 hover:text-discord-foreground"><%= t('analytics.last30days') %></button>
|
|
7
|
+
<button data-days="90" class="date-btn text-sm px-3 py-1 rounded bg-discord-dark-100 text-discord-foreground/70 hover:text-discord-foreground"><%= t('analytics.last90days') %></button>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6">
|
|
12
|
+
<div class="card text-center">
|
|
13
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= guildSummary.totalMessages %></div>
|
|
14
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.messages') %></div>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="card text-center">
|
|
17
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= guildSummary.totalCommands %></div>
|
|
18
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.commands') %></div>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="card text-center">
|
|
21
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= guildSummary.totalJoins %></div>
|
|
22
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.joins') %></div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="card text-center">
|
|
25
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= guildSummary.totalLeaves %></div>
|
|
26
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.leaves') %></div>
|
|
27
|
+
</div>
|
|
28
|
+
<div class="card text-center">
|
|
29
|
+
<div class="text-2xl font-bold text-discord-foreground"><%= Math.round(guildSummary.totalVoiceMinutes) %></div>
|
|
30
|
+
<div class="text-xs text-discord-foreground/60 mt-1"><%= t('analytics.voice') %> (min)</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
35
|
+
<div class="card">
|
|
36
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.messagesPerDay') %></h3>
|
|
37
|
+
<canvas id="chartMessages" width="400" height="200"></canvas>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="card">
|
|
40
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.joinsVsLeaves') %></h3>
|
|
41
|
+
<canvas id="chartMembers" width="400" height="200"></canvas>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="card">
|
|
44
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.voiceActivity') %></h3>
|
|
45
|
+
<canvas id="chartVoice" width="400" height="200"></canvas>
|
|
46
|
+
</div>
|
|
47
|
+
<div class="card">
|
|
48
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.commandsPerDay') %></h3>
|
|
49
|
+
<canvas id="chartCommands" width="400" height="200"></canvas>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
54
|
+
<div class="card">
|
|
55
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.topCommands') %></h3>
|
|
56
|
+
<canvas id="chartTopCommands" width="400" height="200"></canvas>
|
|
57
|
+
</div>
|
|
58
|
+
<% if (typeof slowestCommands !== 'undefined' && slowestCommands.length > 0) { %>
|
|
59
|
+
<div class="card">
|
|
60
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.slowestCommands') %></h3>
|
|
61
|
+
<canvas id="chartSlowestCommands" width="400" height="200"></canvas>
|
|
62
|
+
</div>
|
|
63
|
+
<% } %>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<% if (typeof channelVoice !== 'undefined' && channelVoice.length > 0) { %>
|
|
67
|
+
<div class="card mb-6">
|
|
68
|
+
<h3 class="text-lg font-semibold mb-4 text-discord-foreground"><%= t('analytics.perChannelVoice') %></h3>
|
|
69
|
+
<canvas id="chartChannelVoice" width="600" height="200"></canvas>
|
|
70
|
+
</div>
|
|
71
|
+
<% } %>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
75
|
+
<script>
|
|
76
|
+
(function() {
|
|
77
|
+
const colors = {
|
|
78
|
+
primary: '#5865f2',
|
|
79
|
+
success: '#57f287',
|
|
80
|
+
warning: '#fee75c',
|
|
81
|
+
danger: '#ed4245',
|
|
82
|
+
foreground: '#ffffff',
|
|
83
|
+
foreground60: 'rgba(255,255,255,0.6)',
|
|
84
|
+
dark100: '#36393f',
|
|
85
|
+
dark200: '#2f3136',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
Chart.defaults.color = colors.foreground60;
|
|
89
|
+
Chart.defaults.borderColor = colors.dark100;
|
|
90
|
+
Chart.defaults.plugins.legend.labels.color = colors.foreground60;
|
|
91
|
+
|
|
92
|
+
const data = <%- JSON.stringify(chartData) %>;
|
|
93
|
+
|
|
94
|
+
function createBarChart(canvasId, label, values, keyX, keyY) {
|
|
95
|
+
new Chart(document.getElementById(canvasId), {
|
|
96
|
+
type: 'bar',
|
|
97
|
+
data: {
|
|
98
|
+
labels: values.map(v => v[keyX]),
|
|
99
|
+
datasets: [{
|
|
100
|
+
label: label,
|
|
101
|
+
data: values.map(v => v[keyY]),
|
|
102
|
+
backgroundColor: colors.primary,
|
|
103
|
+
borderRadius: 4,
|
|
104
|
+
}]
|
|
105
|
+
},
|
|
106
|
+
options: { responsive: true, plugins: { legend: { display: false } } }
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function createHorizontalBarChart(canvasId, label, values, keyX, keyY) {
|
|
111
|
+
new Chart(document.getElementById(canvasId), {
|
|
112
|
+
type: 'bar',
|
|
113
|
+
data: {
|
|
114
|
+
labels: values.map(v => v[keyX]),
|
|
115
|
+
datasets: [{
|
|
116
|
+
label: label,
|
|
117
|
+
data: values.map(v => v[keyY]),
|
|
118
|
+
backgroundColor: values.map((_, i) => {
|
|
119
|
+
const alpha = 1 - (i * 0.08);
|
|
120
|
+
return `rgba(88,101,242,${Math.max(0.3, alpha)})`;
|
|
121
|
+
}),
|
|
122
|
+
borderRadius: 4,
|
|
123
|
+
}]
|
|
124
|
+
},
|
|
125
|
+
options: {
|
|
126
|
+
indexAxis: 'y',
|
|
127
|
+
responsive: true,
|
|
128
|
+
plugins: { legend: { display: false } }
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
createBarChart('chartMessages', '<%= t("analytics.messagesPerDay") %>', data.messagesPerDay, 'date', 'count');
|
|
134
|
+
createBarChart('chartVoice', '<%= t("analytics.voiceActivity") %>', data.voicePerDay, 'date', 'count');
|
|
135
|
+
createBarChart('chartCommands', '<%= t("analytics.commandsPerDay") %>', data.commandsPerDay, 'date', 'count');
|
|
136
|
+
createHorizontalBarChart('chartTopCommands', '<%= t("analytics.topCommands") %>', data.topCommands, 'command_name', 'usage_count');
|
|
137
|
+
|
|
138
|
+
new Chart(document.getElementById('chartMembers'), {
|
|
139
|
+
type: 'line',
|
|
140
|
+
data: {
|
|
141
|
+
labels: data.joinsPerDay.map(v => v.date),
|
|
142
|
+
datasets: [
|
|
143
|
+
{
|
|
144
|
+
label: '<%= t("analytics.joins") %>',
|
|
145
|
+
data: data.joinsPerDay.map(v => v.count),
|
|
146
|
+
borderColor: colors.success,
|
|
147
|
+
backgroundColor: 'rgba(87,242,135,0.1)',
|
|
148
|
+
fill: true,
|
|
149
|
+
tension: 0.3,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
label: '<%= t("analytics.leaves") %>',
|
|
153
|
+
data: data.leavesPerDay.map(v => v.count),
|
|
154
|
+
borderColor: colors.danger,
|
|
155
|
+
backgroundColor: 'rgba(237,66,69,0.1)',
|
|
156
|
+
fill: true,
|
|
157
|
+
tension: 0.3,
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
},
|
|
161
|
+
options: { responsive: true }
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
<% if (typeof slowestCommands !== 'undefined' && slowestCommands.length > 0) { %>
|
|
165
|
+
new Chart(document.getElementById('chartSlowestCommands'), {
|
|
166
|
+
type: 'bar',
|
|
167
|
+
data: {
|
|
168
|
+
labels: data.slowestCommands.map(v => v.command_name),
|
|
169
|
+
datasets: [{
|
|
170
|
+
label: '<%= t("analytics.avgExecutionTime") %> (ms)',
|
|
171
|
+
data: data.slowestCommands.map(v => v.avgExecutionTimeMs),
|
|
172
|
+
backgroundColor: colors.warning,
|
|
173
|
+
borderRadius: 4,
|
|
174
|
+
}]
|
|
175
|
+
},
|
|
176
|
+
options: {
|
|
177
|
+
responsive: true,
|
|
178
|
+
plugins: { legend: { display: false } },
|
|
179
|
+
scales: { y: { title: { display: true, text: 'ms' } } }
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
<% } %>
|
|
183
|
+
|
|
184
|
+
<% if (typeof channelVoice !== 'undefined' && channelVoice.length > 0) { %>
|
|
185
|
+
new Chart(document.getElementById('chartChannelVoice'), {
|
|
186
|
+
type: 'bar',
|
|
187
|
+
data: {
|
|
188
|
+
labels: data.channelVoice.map(v => v.channel_id),
|
|
189
|
+
datasets: [{
|
|
190
|
+
label: '<%= t("analytics.totalMinutes") %>',
|
|
191
|
+
data: data.channelVoice.map(v => v.total_minutes),
|
|
192
|
+
backgroundColor: colors.primary,
|
|
193
|
+
borderRadius: 4,
|
|
194
|
+
}]
|
|
195
|
+
},
|
|
196
|
+
options: { responsive: true, plugins: { legend: { display: false } } }
|
|
197
|
+
});
|
|
198
|
+
<% } %>
|
|
199
|
+
|
|
200
|
+
document.querySelectorAll('.date-btn').forEach(btn => {
|
|
201
|
+
btn.addEventListener('click', () => {
|
|
202
|
+
const days = btn.dataset.days;
|
|
203
|
+
const currentUrl = new URL(window.location.href);
|
|
204
|
+
currentUrl.searchParams.set('days', days);
|
|
205
|
+
window.location.href = currentUrl.toString();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
})();
|
|
209
|
+
</script>
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zumito-team/analytics-module",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"main": "index.js",
|
|
5
|
-
"types": "index.d.ts",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
6
|
"description": "Server analytics module with configurable tracking and optional panel integration",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "tsc -p tsconfig.json"
|
|
9
|
+
"build": "tsc -p tsconfig.json && cp -r translations dist/ && cp -r views dist/"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@zumito-team/admin-module": "^1.8.0",
|
package/tsconfig.json
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|