Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
MBetterd
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mbetter
MBetterd
Commits
849b646a
Commit
849b646a
authored
Feb 03, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix PDF export and add printable page formatting
parent
632709c8
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
239 additions
and
78 deletions
+239
-78
routes.py
app/main/routes.py
+152
-78
reports.html
app/templates/main/reports.html
+87
-0
No files found.
app/main/routes.py
View file @
849b646a
...
...
@@ -1910,86 +1910,158 @@ def report_detail(sync_id):
def
export_reports
(
query
,
export_format
):
"""Export reports to various formats"""
try
:
from
app.models
import
ReportSync
,
Bet
,
ExtractionStats
from
app.models
import
ReportSync
,
Bet
,
ExtractionStats
,
APIToken
,
ClientActivity
,
MatchReport
from
sqlalchemy
import
func
,
and_
,
or_
# Get filter parameters from request
client_id_filter
=
request
.
args
.
get
(
'client_id'
,
''
)
.
strip
()
date_range_filter
=
request
.
args
.
get
(
'date_range'
,
'today'
)
.
strip
()
start_date_filter
=
request
.
args
.
get
(
'start_date'
,
''
)
.
strip
()
end_date_filter
=
request
.
args
.
get
(
'end_date'
,
''
)
.
strip
()
start_time_filter
=
request
.
args
.
get
(
'start_time'
,
''
)
.
strip
()
end_time_filter
=
request
.
args
.
get
(
'end_time'
,
''
)
.
strip
()
timezone_filter
=
request
.
args
.
get
(
'timezone'
,
'auto'
)
.
strip
()
# Calculate date range based on filter
now
=
datetime
.
utcnow
()
start_date
=
None
end_date
=
None
if
date_range_filter
==
'today'
:
start_date
=
now
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
now
elif
date_range_filter
==
'yesterday'
:
yesterday
=
now
-
timedelta
(
days
=
1
)
start_date
=
yesterday
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
yesterday
.
replace
(
hour
=
23
,
minute
=
59
,
second
=
59
,
microsecond
=
999999
)
elif
date_range_filter
==
'this_week'
:
start_date
=
now
-
timedelta
(
days
=
now
.
weekday
())
start_date
=
start_date
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
now
elif
date_range_filter
==
'last_week'
:
last_week_end
=
now
-
timedelta
(
days
=
now
.
weekday
()
+
1
)
last_week_start
=
last_week_end
-
timedelta
(
days
=
6
)
start_date
=
last_week_start
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
last_week_end
.
replace
(
hour
=
23
,
minute
=
59
,
second
=
59
,
microsecond
=
999999
)
elif
date_range_filter
==
'this_month'
:
start_date
=
now
.
replace
(
day
=
1
,
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
end_date
=
now
elif
date_range_filter
==
'all'
:
start_date
=
None
end_date
=
None
elif
date_range_filter
==
'custom'
:
if
start_date_filter
:
try
:
start_date
=
datetime
.
strptime
(
start_date_filter
,
'
%
Y-
%
m-
%
d'
)
if
start_time_filter
:
hour
,
minute
=
map
(
int
,
start_time_filter
.
split
(
':'
))
start_date
=
start_date
.
replace
(
hour
=
hour
,
minute
=
minute
,
second
=
0
,
microsecond
=
0
)
else
:
start_date
=
start_date
.
replace
(
hour
=
0
,
minute
=
0
,
second
=
0
,
microsecond
=
0
)
except
ValueError
:
pass
if
end_date_filter
:
try
:
end_date
=
datetime
.
strptime
(
end_date_filter
,
'
%
Y-
%
m-
%
d'
)
if
end_time_filter
:
hour
,
minute
=
map
(
int
,
end_time_filter
.
split
(
':'
))
end_date
=
end_date
.
replace
(
hour
=
hour
,
minute
=
minute
,
second
=
59
,
microsecond
=
999999
)
else
:
end_date
=
end_date
.
replace
(
hour
=
23
,
minute
=
59
,
second
=
59
,
microsecond
=
999999
)
except
ValueError
:
pass
#
Get all reports matching query
reports
=
query
.
all
()
#
Build query for MatchReport data (same as reports page)
base_query
=
MatchReport
.
query
if
not
reports
:
# Apply user filter
if
not
current_user
.
is_admin
:
user_token_ids
=
[
t
.
id
for
t
in
APIToken
.
query
.
filter_by
(
user_id
=
current_user
.
id
)
.
all
()]
if
user_token_ids
:
client_ids
=
[
c
.
rustdesk_id
for
c
in
ClientActivity
.
query
.
filter
(
ClientActivity
.
api_token_id
.
in_
(
user_token_ids
)
)
.
all
()]
if
client_ids
:
base_query
=
base_query
.
filter
(
MatchReport
.
client_id
.
in_
(
client_ids
))
else
:
base_query
=
base_query
.
filter
(
MatchReport
.
client_id
==
'none'
)
else
:
base_query
=
base_query
.
filter
(
MatchReport
.
client_id
==
'none'
)
# Apply filters
if
client_id_filter
:
base_query
=
base_query
.
filter
(
MatchReport
.
client_id
==
client_id_filter
)
if
start_date
:
base_query
=
base_query
.
filter
(
MatchReport
.
match_datetime
>=
start_date
)
if
end_date
:
base_query
=
base_query
.
filter
(
MatchReport
.
match_datetime
<=
end_date
)
# Get all matching match reports for aggregation
matching_reports
=
base_query
.
all
()
if
not
matching_reports
:
flash
(
'No reports to export'
,
'warning'
)
return
redirect
(
url_for
(
'main.reports'
))
# Prepare data for export
export_data
=
[]
for
report
in
reports
:
# Get bets and stats for this report
bets
=
Bet
.
query
.
filter_by
(
sync_id
=
report
.
id
)
.
all
()
stats
=
ExtractionStats
.
query
.
filter_by
(
sync_id
=
report
.
id
)
.
all
()
# Aggregate by client
client_aggregates
=
{}
for
report
in
matching_reports
:
client_id
=
report
.
client_id
if
client_id
not
in
client_aggregates
:
client_activity
=
ClientActivity
.
query
.
filter_by
(
rustdesk_id
=
client_id
)
.
first
()
token_name
=
client_activity
.
api_token
.
name
if
client_activity
and
client_activity
.
api_token
else
'Unknown'
# Add summary row
export_data
.
append
({
'Type'
:
'Summary'
,
'Sync ID'
:
report
.
sync_id
,
'Client ID'
:
report
.
client_id
,
'Sync Timestamp'
:
report
.
sync_timestamp
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
report
.
sync_timestamp
else
''
,
'Date Range'
:
report
.
date_range
,
'Start Date'
:
report
.
start_date
.
strftime
(
'
%
Y-
%
m-
%
d'
)
if
report
.
start_date
else
''
,
'End Date'
:
report
.
end_date
.
strftime
(
'
%
Y-
%
m-
%
d'
)
if
report
.
end_date
else
''
,
'Total Payin'
:
float
(
report
.
total_payin
)
if
report
.
total_payin
else
0.0
,
'Total Payout'
:
float
(
report
.
total_payout
)
if
report
.
total_payout
else
0.0
,
'Net Profit'
:
float
(
report
.
net_profit
)
if
report
.
net_profit
else
0.0
,
'Total Bets'
:
report
.
total_bets
,
'Total Matches'
:
report
.
total_matches
,
'Bet UUID'
:
''
,
'Match ID'
:
''
,
'Outcome'
:
''
,
'Amount'
:
''
,
'Result'
:
''
})
client_aggregates
[
client_id
]
=
{
'client_id'
:
client_id
,
'token_name'
:
token_name
,
'total_payin'
:
0.0
,
'total_payout'
:
0.0
,
'total_bets'
:
0
,
'total_matches'
:
0
,
'winning_bets'
:
0
,
'losing_bets'
:
0
,
'pending_bets'
:
0
,
'cap_balance'
:
0.0
,
'accumulated_shortfall'
:
0.0
,
'last_match_timestamp'
:
report
.
match_datetime
}
# Add bet details
for
bet
in
bets
:
for
detail
in
bet
.
details
:
export_data
.
append
({
'Type'
:
'Bet Detail'
,
'Sync ID'
:
report
.
sync_id
,
'Client ID'
:
report
.
client_id
,
'Sync Timestamp'
:
report
.
sync_timestamp
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
report
.
sync_timestamp
else
''
,
'Date Range'
:
report
.
date_range
,
'Start Date'
:
report
.
start_date
.
strftime
(
'
%
Y-
%
m-
%
d'
)
if
report
.
start_date
else
''
,
'End Date'
:
report
.
end_date
.
strftime
(
'
%
Y-
%
m-
%
d'
)
if
report
.
end_date
else
''
,
'Total Payin'
:
''
,
'Total Payout'
:
''
,
'Net Profit'
:
''
,
'Total Bets'
:
''
,
'Total Matches'
:
''
,
'Bet UUID'
:
bet
.
uuid
,
'Match ID'
:
detail
.
match_id
,
'Outcome'
:
detail
.
outcome
,
'Amount'
:
float
(
detail
.
amount
)
if
detail
.
amount
else
0.0
,
'Result'
:
detail
.
result
})
client_aggregates
[
client_id
][
'total_payin'
]
+=
float
(
report
.
total_payin
)
if
report
.
total_payin
else
0.0
client_aggregates
[
client_id
][
'total_payout'
]
+=
float
(
report
.
total_payout
)
if
report
.
total_payout
else
0.0
client_aggregates
[
client_id
][
'total_bets'
]
+=
report
.
total_bets
client_aggregates
[
client_id
][
'total_matches'
]
+=
1
client_aggregates
[
client_id
][
'winning_bets'
]
+=
report
.
winning_bets
client_aggregates
[
client_id
][
'losing_bets'
]
+=
report
.
losing_bets
client_aggregates
[
client_id
][
'pending_bets'
]
+=
report
.
pending_bets
# Add extraction stats
for
stat
in
stats
:
if
report
.
match_datetime
>=
client_aggregates
[
client_id
][
'last_match_timestamp'
]:
client_aggregates
[
client_id
][
'cap_balance'
]
=
float
(
report
.
cap_compensation_balance
)
if
report
.
cap_compensation_balance
else
0.0
client_aggregates
[
client_id
][
'accumulated_shortfall'
]
=
float
(
report
.
accumulated_shortfall
)
if
report
.
accumulated_shortfall
else
0.0
client_aggregates
[
client_id
][
'last_match_timestamp'
]
=
report
.
match_datetime
# Calculate balance for each client
for
client_id
,
data
in
client_aggregates
.
items
():
data
[
'balance'
]
=
data
[
'total_payin'
]
-
data
[
'total_payout'
]
# Prepare data for export
export_data
=
[]
for
client_id
,
data
in
client_aggregates
.
items
():
export_data
.
append
({
'Type'
:
'Extraction Stats'
,
'Sync ID'
:
report
.
sync_id
,
'Client ID'
:
report
.
client_id
,
'Sync Timestamp'
:
report
.
sync_timestamp
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
report
.
sync_timestamp
else
''
,
'Date Range'
:
report
.
date_range
,
'Start Date'
:
report
.
start_date
.
strftime
(
'
%
Y-
%
m-
%
d'
)
if
report
.
start_date
else
''
,
'End Date'
:
report
.
end_date
.
strftime
(
'
%
Y-
%
m-
%
d'
)
if
report
.
end_date
else
''
,
'Total Payin'
:
float
(
stat
.
total_amount_collected
)
if
stat
.
total_amount_collected
else
0.0
,
'Total Payout'
:
float
(
stat
.
total_redistributed
)
if
stat
.
total_redistributed
else
0.0
,
'Net Profit'
:
''
,
'Total Bets'
:
stat
.
total_bets
,
'Total Matches'
:
''
,
'Bet UUID'
:
''
,
'Match ID'
:
stat
.
match_id
,
'Outcome'
:
stat
.
actual_result
,
'Amount'
:
''
,
'Result'
:
stat
.
extraction_result
'Client Name'
:
data
[
'token_name'
],
'Client ID'
:
data
[
'client_id'
],
'Total Matches'
:
data
[
'total_matches'
],
'Total Bets'
:
data
[
'total_bets'
],
'Winning Bets'
:
data
[
'winning_bets'
],
'Losing Bets'
:
data
[
'losing_bets'
],
'Pending Bets'
:
data
[
'pending_bets'
],
'Total Payin'
:
data
[
'total_payin'
],
'Total Payout'
:
data
[
'total_payout'
],
'Balance'
:
data
[
'balance'
],
'CAP Redistribution'
:
data
[
'cap_balance'
],
'Accumulated Shortfall'
:
data
[
'accumulated_shortfall'
],
'Last Match'
:
data
[
'last_match_timestamp'
]
.
strftime
(
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
if
data
[
'last_match_timestamp'
]
else
''
})
# Create DataFrame
...
...
@@ -2018,11 +2090,9 @@ def export_reports(query, export_format):
)
elif
export_format
==
'pdf'
:
# For PDF, we'll create a simple HTML table and convert it
from
weasyprint
import
HTML
import
tempfile
# Create HTML table
# Create HTML table
with styling
html_table
=
df
.
to_html
(
index
=
False
,
classes
=
'table table-striped table-bordered'
)
html_content
=
f
"""
<!DOCTYPE html>
...
...
@@ -2030,15 +2100,19 @@ def export_reports(query, export_format):
<head>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; }}
h1 {{ color: #333; border-bottom: 2px solid #007bff; padding-bottom: 10px; }}
table {{ border-collapse: collapse; width: 100
%
; margin-bottom: 20px; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background-color: #4CAF50; color: white; }}
tr:nth-child(even) {{ background-color: #f2f2f2; }}
th {{ background-color: #007bff; color: white; padding: 10px; text-align: left; font-weight: bold; }}
td {{ padding: 8px; border: 1px solid #ddd; }}
tr:nth-child(even) {{ background-color: #f9f9f9; }}
.text-right {{ text-align: right; }}
.text-center {{ text-align: center; }}
</style>
</head>
<body>
<h1>Reports Export</h1>
<p>Generated on: {datetime.utcnow().strftime('
%
Y-
%
m-
%
d
%
H:
%
M:
%
S')}</p>
<p>Total Clients: {len(export_data)}</p>
{html_table}
</body>
</html>
...
...
app/templates/main/reports.html
View file @
849b646a
...
...
@@ -158,9 +158,96 @@
margin-top
:
12px
;
}
</style>
/* Print button styles */
.print-btn {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.print-btn:hover {
background: #0056b3;
}
/* Print styles */
@media print {
.print-btn,
.btn-group,
#filterCollapse,
.pagination,
.per-page-selector {
display: none !important;
}
.container {
max-width: 100% !important;
padding: 0 !important;
}
.card {
box-shadow: none !important;
border: 1px solid #ddd !important;
break-inside: avoid;
}
.card-header {
background: #f8f9fa !important;
color: #000 !important;
border-bottom: 1px solid #ddd !important;
}
.table {
font-size: 12px;
}
.table thead th {
background: #f8f9fa !important;
color: #000 !important;
font-weight: bold;
border: 1px solid #ddd !important;
position: static;
}
.table tbody td {
border: 1px solid #ddd !important;
}
.text-success {
color: #000 !important;
font-weight: bold;
}
.text-danger {
color: #000 !important;
font-weight: bold;
}
/* Avoid breaking rows across pages */
tr {
page-break-inside: avoid;
}
/* Add page breaks between sections */
.card {
margin-bottom: 20px;
}
}
{% endblock %}
{% block content %}
<!-- Print button -->
<button
class=
"print-btn"
onclick=
"window.print()"
>
<i
class=
"fas fa-print"
></i>
Print Page
</button>
<div
class=
"container-fluid"
>
<div
class=
"row"
>
<div
class=
"col-12"
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment