mirror of
https://github.com/house-of-vanity/OutFleet.git
synced 2025-08-21 14:37:16 +00:00
This commit is contained in:
@@ -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>
|
76
vpn/views.py
76
vpn/views.py
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user