Added UI features
All checks were successful
Docker hub build / docker (push) Successful in 5m11s

This commit is contained in:
Ultradesu
2025-07-21 00:52:45 +03:00
parent 67f1c4d147
commit 7efe87c1d2
2 changed files with 203 additions and 12 deletions

View File

@@ -211,11 +211,17 @@
} }
.link-header { .link-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 15px; margin-bottom: 15px;
flex-wrap: wrap;
gap: 15px;
} }
.link-info { .link-info {
flex: 1; flex: 1;
min-width: 200px;
} }
.link-comment { .link-comment {
@@ -246,6 +252,70 @@
transform: scale(1.05); transform: scale(1.05);
} }
.recent-count {
color: #9ca3af;
font-size: 0.8rem;
background: rgba(168, 85, 247, 0.1);
padding: 3px 8px;
border-radius: 10px;
border: 1px solid rgba(168, 85, 247, 0.2);
transition: all 0.3s ease;
}
.recent-count:hover {
background: rgba(168, 85, 247, 0.2);
border-color: rgba(168, 85, 247, 0.4);
transform: scale(1.05);
}
.usage-chart {
display: flex;
flex-direction: column;
align-items: center;
background: rgba(0, 0, 0, 0.2);
padding: 12px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
min-width: 120px;
}
.chart-title {
color: #9ca3af;
font-size: 0.7rem;
margin-bottom: 8px;
text-align: center;
font-weight: 500;
}
.chart-bars {
display: flex;
align-items: end;
gap: 2px;
height: 30px;
width: 70px;
}
.chart-bar {
background: linear-gradient(to top, #4ade80, #22c55e);
width: 8px;
border-radius: 2px 2px 0 0;
transition: all 0.3s ease;
min-height: 2px;
opacity: 0.7;
transform-origin: bottom;
}
.chart-bar:hover {
opacity: 1;
transform: scaleY(1.1);
background: linear-gradient(to top, #22c55e, #16a34a);
}
.chart-bar.zero {
background: rgba(107, 114, 128, 0.3);
height: 2px !important;
}
.link-url { .link-url {
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.5);
padding: 12px; padding: 12px;
@@ -328,6 +398,20 @@
.link-stats { .link-stats {
gap: 8px; gap: 8px;
} }
.usage-chart {
min-width: 100px;
padding: 8px;
}
.chart-bars {
width: 60px;
height: 25px;
}
.chart-bar {
width: 6px;
}
} }
.no-servers { .no-servers {
@@ -407,6 +491,15 @@
<div class="link-comment">📱 {{ link_data.comment }}</div> <div class="link-comment">📱 {{ link_data.comment }}</div>
<div class="link-stats"> <div class="link-stats">
<span class="usage-count">✨ {{ link_data.connections }} uses</span> <span class="usage-count">✨ {{ link_data.connections }} uses</span>
<span class="recent-count">📅 {{ link_data.recent_connections }} last 30 days</span>
</div>
</div>
<div class="usage-chart" data-usage="{{ link_data.daily_usage|join:',' }}" data-max="{{ link_data.max_daily }}">
<div class="chart-title">7-day activity</div>
<div class="chart-bars">
{% for day_usage in link_data.daily_usage %}
<div class="chart-bar" data-height="{{ day_usage }}" data-max="{{ link_data.max_daily }}"></div>
{% endfor %}
</div> </div>
</div> </div>
</div> </div>
@@ -499,6 +592,10 @@
50% { transform: scale(1.1); } 50% { transform: scale(1.1); }
100% { transform: scale(1); } 100% { transform: scale(1); }
} }
@keyframes barGrow {
from { transform: scaleY(0); }
to { transform: scaleY(1); }
}
`; `;
document.head.appendChild(style); document.head.appendChild(style);
@@ -507,6 +604,9 @@
// Add some interactivity on load // Add some interactivity on load
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize chart bars
initializeCharts();
// Animate cards on load // Animate cards on load
const cards = document.querySelectorAll('.server-card'); const cards = document.querySelectorAll('.server-card');
cards.forEach((card, index) => { cards.forEach((card, index) => {
@@ -542,14 +642,51 @@
// Add pulse animation to connection counts // Add pulse animation to connection counts
setTimeout(() => { setTimeout(() => {
const connectionCounts = document.querySelectorAll('.connection-count, .usage-count'); const connectionCounts = document.querySelectorAll('.connection-count, .usage-count, .recent-count');
connectionCounts.forEach((count, index) => { connectionCounts.forEach((count, index) => {
setTimeout(() => { setTimeout(() => {
count.style.animation = 'pulse 0.6s ease-in-out'; count.style.animation = 'pulse 0.6s ease-in-out';
}, index * 100); }, index * 100);
}); });
}, 1000); }, 1000);
// Animate chart bars
setTimeout(() => {
const chartBars = document.querySelectorAll('.chart-bar');
chartBars.forEach((bar, index) => {
setTimeout(() => {
bar.style.animation = 'barGrow 0.8s ease-out';
}, index * 50);
});
}, 1500);
}); });
function initializeCharts() {
const charts = document.querySelectorAll('.usage-chart');
charts.forEach(chart => {
const maxValue = parseInt(chart.dataset.max) || 1;
const bars = chart.querySelectorAll('.chart-bar');
bars.forEach(bar => {
const height = parseInt(bar.dataset.height) || 0;
const maxHeight = parseInt(bar.dataset.max) || 1;
if (height === 0) {
bar.classList.add('zero');
bar.style.height = '2px';
} else {
// Calculate height as percentage of container (30px max)
const percentage = Math.max(10, (height / Math.max(maxHeight, 1)) * 100);
const pixelHeight = Math.max(3, (percentage / 100) * 28); // 28px max for padding
bar.style.height = pixelHeight + 'px';
// Add tooltip
bar.title = `${height} connections`;
}
});
});
}
</script> </script>
</body> </body>
</html> </html>

View File

@@ -23,11 +23,17 @@ def userPortal(request, user_hash):
# Get connection statistics for all user's links # Get connection statistics for all user's links
# Count successful connections for each specific link # Count successful connections for each specific link
connection_stats = {} connection_stats = {}
recent_connection_stats = {}
usage_frequency = {}
total_connections = 0 total_connections = 0
# Get date ranges for analysis
from datetime import datetime, timedelta
now = datetime.now()
recent_date = now - timedelta(days=30)
for acl_link in acl_links: for acl_link in acl_links:
# Count successful connections for this specific link by checking if the link appears in the access log data # Count successful connections for this specific link by checking if the link appears in the access log data
# This is more accurate as it counts actual uses of the specific link
link_connections = AccessLog.objects.filter( link_connections = AccessLog.objects.filter(
user=user.username, user=user.username,
server=acl_link.acl.server.name, server=acl_link.acl.server.name,
@@ -37,15 +43,55 @@ def userPortal(request, user_hash):
params=[f'%{acl_link.link}%'] params=[f'%{acl_link.link}%']
).count() ).count()
# Get recent connections (last 30 days)
recent_connections = AccessLog.objects.filter(
user=user.username,
server=acl_link.acl.server.name,
action='Success',
timestamp__gte=recent_date
).extra(
where=["data LIKE %s"],
params=[f'%{acl_link.link}%']
).count()
# Calculate usage frequency (connections per week over last 30 days)
# Get daily usage for the last 7 days for mini chart
daily_usage = []
for i in range(7):
day_start = now - timedelta(days=i+1)
day_end = now - timedelta(days=i)
day_connections = AccessLog.objects.filter(
user=user.username,
server=acl_link.acl.server.name,
action='Success',
timestamp__gte=day_start,
timestamp__lt=day_end
).extra(
where=["data LIKE %s"],
params=[f'%{acl_link.link}%']
).count()
daily_usage.append(day_connections)
# Reverse to show oldest to newest
daily_usage.reverse()
# If no specific link matches found, fall back to general server connection count for this user # If no specific link matches found, fall back to general server connection count for this user
if link_connections == 0: if link_connections == 0:
# This gives a rough estimate based on server connections divided by number of links for this server
server_connections = AccessLog.objects.filter( server_connections = AccessLog.objects.filter(
user=user.username, user=user.username,
server=acl_link.acl.server.name, server=acl_link.acl.server.name,
action='Success' action='Success'
).count() ).count()
server_recent_connections = AccessLog.objects.filter(
user=user.username,
server=acl_link.acl.server.name,
action='Success',
timestamp__gte=recent_date
).count()
# Get number of links for this server for this user # Get number of links for this server for this user
user_links_on_server = ACLLink.objects.filter( user_links_on_server = ACLLink.objects.filter(
acl__user=user, acl__user=user,
@@ -55,8 +101,17 @@ def userPortal(request, user_hash):
# Distribute connections evenly among links if we can't track specific usage # Distribute connections evenly among links if we can't track specific usage
if user_links_on_server > 0: if user_links_on_server > 0:
link_connections = server_connections // user_links_on_server link_connections = server_connections // user_links_on_server
recent_connections = server_recent_connections // user_links_on_server
# Distribute daily usage as well
if sum(daily_usage) == 0: # If no activity, create empty chart
daily_usage = [0] * 7
else:
avg_daily = max(1, sum(daily_usage) // (user_links_on_server * 7))
daily_usage = [avg_daily if sum(daily_usage) > 0 else 0 for _ in range(7)]
connection_stats[acl_link.link] = link_connections connection_stats[acl_link.link] = link_connections
recent_connection_stats[acl_link.link] = recent_connections
usage_frequency[acl_link.link] = daily_usage
total_connections += link_connections total_connections += link_connections
# Group links by server # Group links by server
@@ -92,24 +147,23 @@ def userPortal(request, user_hash):
# Add link information with connection stats # Add link information with connection stats
link_url = f"{EXTERNAL_ADDRESS}/ss/{link.link}#{server_name}" link_url = f"{EXTERNAL_ADDRESS}/ss/{link.link}#{server_name}"
connection_count = connection_stats.get(link.link, 0) connection_count = connection_stats.get(link.link, 0)
recent_count = recent_connection_stats.get(link.link, 0)
daily_usage = usage_frequency.get(link.link, [0] * 7)
servers_data[server_name]['links'].append({ servers_data[server_name]['links'].append({
'link': link, 'link': link,
'url': link_url, 'url': link_url,
'comment': link.comment or 'Default', 'comment': link.comment or 'Default',
'connections': connection_count, 'connections': connection_count,
'recent_connections': recent_count,
'daily_usage': daily_usage,
'max_daily': max(daily_usage) if daily_usage else 0,
}) })
servers_data[server_name]['total_connections'] += connection_count servers_data[server_name]['total_connections'] += connection_count
total_links += 1 total_links += 1
# Get recent connection activity (last 30 days) # Calculate total recent connections from all links
from datetime import datetime, timedelta total_recent_connections = sum(recent_connection_stats.values())
recent_date = datetime.now() - timedelta(days=30)
recent_connections = AccessLog.objects.filter(
user=user.username,
action='Success',
timestamp__gte=recent_date
).count()
context = { context = {
'user': user, 'user': user,
@@ -117,7 +171,7 @@ def userPortal(request, user_hash):
'total_servers': len(servers_data), 'total_servers': len(servers_data),
'total_links': total_links, 'total_links': total_links,
'total_connections': total_connections, 'total_connections': total_connections,
'recent_connections': recent_connections, 'recent_connections': total_recent_connections,
'external_address': EXTERNAL_ADDRESS, 'external_address': EXTERNAL_ADDRESS,
} }