mirror of
https://github.com/phishingclub/phishingclub.git
synced 2026-07-07 04:47:55 +02:00
update custom stats table and db
Signed-off-by: Ronni Skansing <rskansing@gmail.com>
This commit is contained in:
@@ -37,12 +37,6 @@ type CampaignStats struct {
|
||||
DataSubmissions int `gorm:"not null;default:0" json:"dataSubmissions"` // Form submissions
|
||||
Reported int `gorm:"not null;default:0" json:"reported"` // Reported phishing
|
||||
|
||||
// Success rates (as percentages for quick display)
|
||||
OpenRate float64 `gorm:"not null;default:0" json:"openRate"`
|
||||
ClickRate float64 `gorm:"not null;default:0" json:"clickRate"`
|
||||
SubmissionRate float64 `gorm:"not null;default:0" json:"submissionRate"`
|
||||
ReportRate float64 `gorm:"not null;default:0" json:"reportRate"`
|
||||
|
||||
// Campaign metadata
|
||||
TemplateName string `gorm:"" json:"templateName"`
|
||||
CampaignType string `gorm:"" json:"campaignType"` // 'scheduled', 'self-managed'
|
||||
|
||||
@@ -3733,19 +3733,6 @@ func (c *Campaign) GenerateCampaignStats(ctx context.Context, session *model.Ses
|
||||
}
|
||||
c.Logger.Debugf("retrieved result stats", "campaignID", campaignID.String(), "recipients", resultStats.Recipients)
|
||||
|
||||
// Calculate rates
|
||||
openRate := float64(0)
|
||||
clickRate := float64(0)
|
||||
submissionRate := float64(0)
|
||||
reportRate := float64(0)
|
||||
|
||||
if resultStats.Recipients > 0 {
|
||||
openRate = (float64(resultStats.TrackingPixelLoaded) / float64(resultStats.Recipients)) * 100
|
||||
clickRate = (float64(resultStats.WebsiteLoaded) / float64(resultStats.Recipients)) * 100
|
||||
submissionRate = (float64(resultStats.SubmittedData) / float64(resultStats.Recipients)) * 100
|
||||
reportRate = (float64(resultStats.Reported) / float64(resultStats.Recipients)) * 100
|
||||
}
|
||||
|
||||
// Determine campaign type
|
||||
campaignType := "scheduled"
|
||||
if campaign.SendStartAt == nil && campaign.SendEndAt == nil {
|
||||
@@ -3805,10 +3792,6 @@ func (c *Campaign) GenerateCampaignStats(ctx context.Context, session *model.Ses
|
||||
WebsiteVisits: int(resultStats.WebsiteLoaded),
|
||||
DataSubmissions: int(resultStats.SubmittedData),
|
||||
Reported: int(resultStats.Reported),
|
||||
OpenRate: openRate,
|
||||
ClickRate: clickRate,
|
||||
SubmissionRate: submissionRate,
|
||||
ReportRate: reportRate,
|
||||
|
||||
TemplateName: templateName,
|
||||
CampaignType: campaignType,
|
||||
@@ -3905,14 +3888,6 @@ func (c *Campaign) CreateManualCampaignStats(ctx context.Context, session *model
|
||||
req.UpdatedAt = &statsDate
|
||||
req.CampaignID = nil // No campaign reference for manual stats
|
||||
|
||||
// Calculate rates
|
||||
if req.TotalRecipients > 0 {
|
||||
req.OpenRate = float64(req.TrackingPixelLoaded) / float64(req.TotalRecipients) * 100
|
||||
req.ClickRate = float64(req.WebsiteVisits) / float64(req.TotalRecipients) * 100
|
||||
req.SubmissionRate = float64(req.DataSubmissions) / float64(req.TotalRecipients) * 100
|
||||
req.ReportRate = float64(req.Reported) / float64(req.TotalRecipients) * 100
|
||||
}
|
||||
|
||||
// Calculate total events
|
||||
req.TotalEvents = req.EmailsSent + req.TrackingPixelLoaded + req.WebsiteVisits + req.DataSubmissions + req.Reported
|
||||
|
||||
@@ -3988,14 +3963,6 @@ func (c *Campaign) UpdateManualCampaignStats(ctx context.Context, session *model
|
||||
req.UpdatedAt = &now
|
||||
req.CampaignID = nil // Ensure it remains manual
|
||||
|
||||
// Calculate rates
|
||||
if req.TotalRecipients > 0 {
|
||||
req.OpenRate = float64(req.TrackingPixelLoaded) / float64(req.TotalRecipients) * 100
|
||||
req.ClickRate = float64(req.WebsiteVisits) / float64(req.TotalRecipients) * 100
|
||||
req.SubmissionRate = float64(req.DataSubmissions) / float64(req.TotalRecipients) * 100
|
||||
req.ReportRate = float64(req.Reported) / float64(req.TotalRecipients) * 100
|
||||
}
|
||||
|
||||
// Calculate total events
|
||||
req.TotalEvents = req.EmailsSent + req.TrackingPixelLoaded + req.WebsiteVisits + req.DataSubmissions + req.Reported
|
||||
|
||||
@@ -4014,10 +3981,6 @@ func (c *Campaign) UpdateManualCampaignStats(ctx context.Context, session *model
|
||||
"website_visits": req.WebsiteVisits,
|
||||
"data_submissions": req.DataSubmissions,
|
||||
"reported": req.Reported,
|
||||
"open_rate": req.OpenRate,
|
||||
"click_rate": req.ClickRate,
|
||||
"submission_rate": req.SubmissionRate,
|
||||
"report_rate": req.ReportRate,
|
||||
"template_name": req.TemplateName,
|
||||
"campaign_type": req.CampaignType,
|
||||
}
|
||||
|
||||
@@ -219,17 +219,19 @@
|
||||
(a, b) => new Date(a.campaignClosedAt).getTime() - new Date(b.campaignClosedAt).getTime()
|
||||
);
|
||||
|
||||
// calculate percentage with one decimal place
|
||||
function pct(n, d) {
|
||||
return d > 0 ? Math.round((n / d) * 1000) / 10 : 0;
|
||||
}
|
||||
|
||||
return sortedStats.map((stat, index) => ({
|
||||
index: index + 1,
|
||||
date: stat.campaignClosedAt ? new Date(stat.campaignClosedAt) : null,
|
||||
name: stat.campaignName || `Campaign ${index + 1}`,
|
||||
// If your backend provides fractions (e.g., 0.425 for 42.5%), multiply by 100 below.
|
||||
// If it produces percentages (e.g., 42.5 for 42.5%), leave as is.
|
||||
openRate: Math.round((stat.openRate || 0) * (stat.openRate > 1 ? 1 : 100) * 10) / 10,
|
||||
clickRate: Math.round((stat.clickRate || 0) * (stat.clickRate > 1 ? 1 : 100) * 10) / 10,
|
||||
submissionRate:
|
||||
Math.round((stat.submissionRate || 0) * (stat.submissionRate > 1 ? 1 : 100) * 10) / 10,
|
||||
reportRate: Math.round((stat.reportRate || 0) * (stat.reportRate > 1 ? 1 : 100) * 10) / 10,
|
||||
openRate: pct(stat.trackingPixelLoaded, stat.totalRecipients),
|
||||
clickRate: pct(stat.websiteVisits, stat.totalRecipients),
|
||||
submissionRate: pct(stat.dataSubmissions, stat.totalRecipients),
|
||||
reportRate: pct(stat.reported, stat.totalRecipients),
|
||||
totalRecipients: stat.totalRecipients
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -7,12 +7,16 @@
|
||||
export let value = '';
|
||||
export let isDate = false;
|
||||
export let hideHours = false;
|
||||
export let isRelative = false; // new prop to toggle relative time
|
||||
export let isRelative = false; // relative time
|
||||
export let alignText = '';
|
||||
</script>
|
||||
|
||||
<td
|
||||
class={`pl-4 font-regular text-slate-600 dark:text-gray-200 text-ellipsis whitespace-nowrap overflow-hidden pr-4 transition-colors duration-200`}
|
||||
title={isDate ? '' : value}
|
||||
class:text-left={alignText === 'left'}
|
||||
class:text-center={alignText === 'center'}
|
||||
class:text-right={alignText === 'right'}
|
||||
>
|
||||
{#if value}
|
||||
{#if isDate}
|
||||
|
||||
@@ -25,6 +25,41 @@
|
||||
import FormColumns from '$lib/components/FormColumns.svelte';
|
||||
import FormColumn from '$lib/components/FormColumn.svelte';
|
||||
|
||||
function getStatPercentages(stats) {
|
||||
const totalRecipients = stats.totalRecipients || 0;
|
||||
const emailsSent = stats.emailsSent || 0;
|
||||
const read = stats.trackingPixelLoaded || 0;
|
||||
const clicked = stats.websiteVisits || 0;
|
||||
const reported = stats.reported || 0;
|
||||
|
||||
function pct(n, d) {
|
||||
return d > 0 ? Math.round((n / d) * 100) : 0;
|
||||
}
|
||||
|
||||
return {
|
||||
sent: {
|
||||
count: emailsSent,
|
||||
absolute: pct(emailsSent, totalRecipients),
|
||||
relative: pct(emailsSent, totalRecipients)
|
||||
},
|
||||
read: {
|
||||
count: read,
|
||||
absolute: pct(read, totalRecipients),
|
||||
relative: pct(read, emailsSent)
|
||||
},
|
||||
clicked: {
|
||||
count: clicked,
|
||||
absolute: pct(clicked, totalRecipients),
|
||||
relative: pct(clicked, read)
|
||||
},
|
||||
reported: {
|
||||
count: reported,
|
||||
absolute: pct(reported, totalRecipients),
|
||||
relative: pct(reported, emailsSent)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Get company ID from URL params
|
||||
$: companyId = $page.params.id;
|
||||
|
||||
@@ -299,15 +334,52 @@
|
||||
|
||||
<BigButton on:click={openCreateModal}>Add</BigButton>
|
||||
|
||||
<script>
|
||||
function getStatPercentages(stats) {
|
||||
const totalRecipients = stats.totalRecipients || 0;
|
||||
const emailsSent = stats.emailsSent || 0;
|
||||
const read = stats.trackingPixelLoaded || 0;
|
||||
const clicked = stats.websiteVisits || 0;
|
||||
const reported = stats.reported || 0;
|
||||
|
||||
function pct(n, d) {
|
||||
return d > 0 ? Math.round((n / d) * 100) : 0;
|
||||
}
|
||||
|
||||
return {
|
||||
sent: {
|
||||
count: emailsSent,
|
||||
absolute: pct(emailsSent, totalRecipients),
|
||||
relative: pct(emailsSent, totalRecipients)
|
||||
},
|
||||
read: {
|
||||
count: read,
|
||||
absolute: pct(read, totalRecipients),
|
||||
relative: pct(read, emailsSent)
|
||||
},
|
||||
clicked: {
|
||||
count: clicked,
|
||||
absolute: pct(clicked, totalRecipients),
|
||||
relative: pct(clicked, read)
|
||||
},
|
||||
reported: {
|
||||
count: reported,
|
||||
absolute: pct(reported, totalRecipients),
|
||||
relative: pct(reported, emailsSent)
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<Table
|
||||
columns={[
|
||||
{ column: 'Campaign Name', size: 'large' },
|
||||
{ column: 'Recipients', size: 'small' },
|
||||
{ column: 'Open Rate', size: 'small' },
|
||||
{ column: 'Click Rate', size: 'small' },
|
||||
{ column: 'Submission Rate', size: 'small' },
|
||||
{ column: 'Report Rate', size: 'small' },
|
||||
{ column: 'Date', size: 'small' }
|
||||
{ column: 'Recipients', size: 'small', alignText: 'center' },
|
||||
{ column: 'Sent', size: 'small', alignText: 'center' },
|
||||
{ column: 'Read', size: 'small', alignText: 'center' },
|
||||
{ column: 'Clicked', size: 'small', alignText: 'center' },
|
||||
{ column: 'Reported', size: 'small', alignText: 'center' },
|
||||
{ column: 'Time ago', size: 'small', alignText: 'center' }
|
||||
]}
|
||||
sortable={[]}
|
||||
hasData={!!customStats.length}
|
||||
@@ -315,6 +387,7 @@
|
||||
isGhost={isTableLoading}
|
||||
>
|
||||
{#each customStats as stats}
|
||||
{@const pct = getStatPercentages(stats)}
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<button
|
||||
@@ -324,12 +397,18 @@
|
||||
{stats.campaignName}
|
||||
</button>
|
||||
</TableCell>
|
||||
<TableCell value={stats.totalRecipients} />
|
||||
<TableCell value="{Math.round(stats.openRate)}%" />
|
||||
<TableCell value="{Math.round(stats.clickRate)}%" />
|
||||
<TableCell value="{Math.round(stats.submissionRate)}%" />
|
||||
<TableCell value="{Math.round(stats.reportRate)}%" />
|
||||
<TableCell value={stats.createdAt} isDate isRelative />
|
||||
<TableCell alignText="center" value={stats.totalRecipients} />
|
||||
<TableCell alignText="center" value={pct.sent.count} />
|
||||
<TableCell alignText="center" value={`${pct.read.count} (${pct.read.absolute}%)`} />
|
||||
<TableCell
|
||||
alignText="center"
|
||||
value={`${pct.clicked.count} (${pct.clicked.absolute}%, rel: ${pct.clicked.relative}%)`}
|
||||
/>
|
||||
<TableCell
|
||||
alignText="center"
|
||||
value={`${pct.reported.count} (${pct.reported.absolute}%, rel: ${pct.reported.relative}%)`}
|
||||
/>
|
||||
<TableCell alignText="center" value={stats.createdAt} isDate isRelative />
|
||||
<TableCellEmpty />
|
||||
<TableCellAction>
|
||||
<TableDropDownEllipsis>
|
||||
|
||||
Reference in New Issue
Block a user