mirror of
https://github.com/Kizuren/uLinkShortener.git
synced 2025-12-22 05:26:29 +01:00
319 lines
11 KiB
JavaScript
319 lines
11 KiB
JavaScript
let currentAccount = '';
|
|
let refreshInterval;
|
|
|
|
function showError(elementId, message) {
|
|
const errorElement = document.getElementById(elementId);
|
|
errorElement.textContent = message;
|
|
errorElement.style.display = 'block';
|
|
errorElement.classList.add('message-fade');
|
|
setTimeout(() => {
|
|
errorElement.style.display = 'none';
|
|
errorElement.classList.remove('message-fade');
|
|
}, 5000);
|
|
}
|
|
|
|
function showSuccess(elementId, message) {
|
|
const successElement = document.getElementById(elementId);
|
|
successElement.textContent = message;
|
|
successElement.style.display = 'block';
|
|
successElement.classList.add('message-fade');
|
|
setTimeout(() => {
|
|
successElement.style.display = 'none';
|
|
successElement.classList.remove('message-fade');
|
|
}, 5000);
|
|
}
|
|
|
|
// Check for existing login on page load
|
|
window.addEventListener('load', () => {
|
|
const accountId = document.cookie
|
|
.split('; ')
|
|
.find(row => row.startsWith('account_id='))
|
|
?.split('=')[1];
|
|
|
|
if (accountId) {
|
|
handleLogin(accountId);
|
|
}
|
|
setInterval(refreshPublicStats, 5000);
|
|
});
|
|
|
|
async function refreshPublicStats() {
|
|
const response = await fetch('/');
|
|
const text = await response.text();
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(text, 'text/html');
|
|
|
|
const statsScript = Array.from(doc.scripts)
|
|
.find(script => script.textContent.includes('window.stats'));
|
|
|
|
if (statsScript) {
|
|
const statsMatch = statsScript.textContent.match(/window\.stats = (.*?);/);
|
|
if (statsMatch) {
|
|
window.stats = JSON.parse(statsMatch[1]);
|
|
createCharts();
|
|
}
|
|
}
|
|
}
|
|
|
|
async function register() {
|
|
const response = await fetch('/register', { method: 'POST' });
|
|
const data = await response.json();
|
|
await handleLogin(data.account_id);
|
|
}
|
|
|
|
async function handleLogin(accountId) {
|
|
currentAccount = accountId;
|
|
document.getElementById('auth-section').style.display = 'none';
|
|
document.getElementById('url-section').style.display = 'block';
|
|
document.getElementById('current-account-display').textContent = accountId;
|
|
loadAnalytics();
|
|
refreshInterval = setInterval(loadAnalytics, 5000);
|
|
}
|
|
|
|
async function login() {
|
|
const accountId = document.getElementById('account-id').value;
|
|
const response = await fetch('/login', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({account_id: accountId})
|
|
});
|
|
|
|
if (response.ok) {
|
|
await handleLogin(accountId);
|
|
} else {
|
|
showError('auth-error', 'Invalid account ID');
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
clearInterval(refreshInterval);
|
|
const response = await fetch('/logout', { method: 'POST' });
|
|
|
|
if (response.ok) {
|
|
currentAccount = '';
|
|
document.getElementById('auth-section').style.display = 'block';
|
|
document.getElementById('url-section').style.display = 'none';
|
|
document.getElementById('account-id').value = '';
|
|
const resultDiv = document.getElementById('result');
|
|
resultDiv.innerHTML = '';
|
|
resultDiv.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function isValidUrl(url) {
|
|
if (!url || !url.trim()) return false;
|
|
try {
|
|
const urlObj = new URL(url);
|
|
return urlObj.protocol === 'http:' || urlObj.protocol === 'https:';
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function createShortUrl() {
|
|
const url = document.getElementById('url-input').value;
|
|
const resultDiv = document.getElementById('result');
|
|
|
|
if (!isValidUrl(url)) {
|
|
showError('url-error', 'Please enter a valid URL starting with http:// or https://');
|
|
return;
|
|
}
|
|
|
|
const response = await fetch('/create', {
|
|
method: 'POST',
|
|
headers: {'Content-Type': 'application/json'},
|
|
body: JSON.stringify({
|
|
account_id: currentAccount,
|
|
url: url
|
|
})
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (response.ok) {
|
|
const shortUrl = `${window.location.origin}${data.short_url}`;
|
|
showSuccess('url-success', `URL shortened successfully!`);
|
|
resultDiv.innerHTML = `<p>Short URL: <a href="${shortUrl}" target="_blank">${shortUrl}</a></p>`;
|
|
resultDiv.style.display = 'block';
|
|
document.getElementById('url-input').value = '';
|
|
loadAnalytics();
|
|
} else {
|
|
showError('url-error', data.error);
|
|
resultDiv.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
let deleteCallback = null;
|
|
|
|
function showDeleteDialog(shortId) {
|
|
const dialog = document.getElementById('deleteDialog');
|
|
dialog.style.display = 'flex';
|
|
|
|
const confirmBtn = document.getElementById('confirmDelete');
|
|
deleteCallback = async () => {
|
|
const response = await fetch(`/delete/${shortId}`, { method: 'DELETE' });
|
|
|
|
if (response.ok) {
|
|
showSuccess('url-success', 'Link deleted successfully');
|
|
loadAnalytics();
|
|
} else {
|
|
const data = await response.json();
|
|
showError('url-error', data.error || 'Failed to delete link');
|
|
}
|
|
closeDeleteDialog();
|
|
};
|
|
|
|
confirmBtn.onclick = deleteCallback;
|
|
}
|
|
|
|
function closeDeleteDialog() {
|
|
const dialog = document.getElementById('deleteDialog');
|
|
dialog.style.display = 'none';
|
|
deleteCallback = null;
|
|
}
|
|
|
|
async function loadAnalytics() {
|
|
const response = await fetch(`/analytics/${currentAccount}`);
|
|
const data = await response.json();
|
|
|
|
const openDetails = Array.from(document.querySelectorAll('details[open]')).map(
|
|
detail => detail.getAttribute('data-visit-id')
|
|
);
|
|
|
|
const analyticsDiv = document.getElementById('analytics');
|
|
analyticsDiv.innerHTML = '<h2>Your Analytics</h2>';
|
|
|
|
data.links.forEach(link => {
|
|
const linkAnalytics = data.analytics.filter(a => a.link_id === link.short_id);
|
|
const clicks = linkAnalytics.length;
|
|
const shortUrl = `${window.location.origin}/l/${link.short_id}`;
|
|
|
|
analyticsDiv.innerHTML += `
|
|
<div class="link-stats">
|
|
<div class="link-header">
|
|
<h3>Short URL: <a href="${shortUrl}" target="_blank">${link.short_id}</a></h3>
|
|
<button onclick="showDeleteDialog('${link.short_id}')" class="delete-btn">Delete</button>
|
|
</div>
|
|
<p>Target: <a href="${link.target_url}" target="_blank">${link.target_url}</a></p>
|
|
<p>Total Clicks: ${clicks}</p>
|
|
<table class="analytics-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Time</th>
|
|
<th>IP (Port)</th>
|
|
<th>Location</th>
|
|
<th>Device Info</th>
|
|
<th>Browser Info</th>
|
|
<th>Additional Info</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${linkAnalytics.map(visit => {
|
|
const visitId = `${link.short_id}-${visit.timestamp.$date || visit.timestamp}`;
|
|
return `
|
|
<tr>
|
|
<td>${new Date(visit.timestamp.$date || visit.timestamp).toLocaleString()}</td>
|
|
<td>
|
|
${visit.ip}<br>
|
|
Port: ${visit.remote_port}<br>
|
|
${visit.ip_version}
|
|
</td>
|
|
<td>
|
|
Country: ${visit.country}<br>
|
|
ISP: ${visit.isp}
|
|
</td>
|
|
<td>
|
|
OS: ${visit.platform}<br>
|
|
Screen: ${visit.screen_size}<br>
|
|
Window: ${visit.window_size}
|
|
</td>
|
|
<td>
|
|
${visit.browser} ${visit.version}<br>
|
|
Lang: ${visit.language}
|
|
</td>
|
|
<td>
|
|
<details data-visit-id="${visitId}" ${openDetails.includes(visitId) ? 'open' : ''}>
|
|
<summary>More Info</summary>
|
|
<p>User Agent: ${visit.user_agent}</p>
|
|
<p>Referrer: ${visit.referrer}</p>
|
|
<p>Accept: ${visit.accept}</p>
|
|
<p>Accept-Language: ${visit.accept_language}</p>
|
|
<p>Accept-Encoding: ${visit.accept_encoding}</p>
|
|
</details>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}).join('')}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
function createCharts() {
|
|
if (!window.stats?.chart_data) return;
|
|
|
|
const chartConfigs = {
|
|
'ipChart': {
|
|
data: window.stats.chart_data.ip_versions,
|
|
title: 'IP Versions'
|
|
},
|
|
'osChart': {
|
|
data: window.stats.chart_data.os_stats,
|
|
title: 'Operating Systems'
|
|
},
|
|
'countryChart': {
|
|
data: window.stats.chart_data.country_stats,
|
|
title: 'Countries'
|
|
},
|
|
'ispChart': {
|
|
data: window.stats.chart_data.isp_stats,
|
|
title: 'ISPs'
|
|
}
|
|
};
|
|
|
|
Object.entries(chartConfigs).forEach(([chartId, config]) => {
|
|
const ctx = document.getElementById(chartId);
|
|
if (ctx) {
|
|
new Chart(ctx, {
|
|
type: 'pie',
|
|
data: {
|
|
labels: config.data.map(item => item._id || 'Unknown'),
|
|
datasets: [{
|
|
data: config.data.map(item => item.count),
|
|
backgroundColor: [
|
|
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF',
|
|
'#FF9F40', '#4BC0C0', '#9966FF', '#C9CBCF', '#36A2EB'
|
|
]
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: {
|
|
position: 'bottom',
|
|
labels: {
|
|
color: '#ffffff'
|
|
}
|
|
},
|
|
title: {
|
|
display: true,
|
|
text: config.title,
|
|
color: '#ffffff'
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
// Add event listener to close dialog when clicking outside
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const dialog = document.getElementById('deleteDialog');
|
|
dialog.addEventListener('click', (e) => {
|
|
if (e.target === dialog) {
|
|
closeDeleteDialog();
|
|
}
|
|
});
|
|
});
|