Initial Commit
This commit is contained in:
254
templates/index.html
Normal file
254
templates/index.html
Normal file
@ -0,0 +1,254 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Python requirements.txt Generator</title>
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.drop-zone {
|
||||
border: 2px dashed #cbd5e0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.drop-zone.dragover {
|
||||
border-color: #4299e1;
|
||||
background-color: #ebf8ff;
|
||||
}
|
||||
.results-box {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<div class="min-h-screen p-6">
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<h1 class="text-3xl font-bold text-gray-800 mb-8">Python requirements.txt Generator</h1>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h3 class="text-1xl font-bold text-gray-800 mb-2">About this tool</h3>
|
||||
<p>
|
||||
This tool is used to help you find out what original python package versions were used if you are missing a requirements.txt or it is
|
||||
not included in the program's documentation. In most cases, it will get you 99% of the way there, so depreciations and behavior changes
|
||||
won't bite you as hard. Take these estimations with a grain of salt.
|
||||
</p>
|
||||
<br>
|
||||
<ul>
|
||||
<li>1. Use an original unmodified file - due to JS limitations (skill issue), it cannot use the creation date and it will improperly resolve
|
||||
unless there's a git repo in a .zip.
|
||||
</li>
|
||||
<li>2. Relative imports may not be resolvable - you will need to check this manually</li>
|
||||
<li>2a. Some relative imports with common generic names (tools, utils) will incorrectly resolve to a pip package when they are actually
|
||||
a relative import.
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
<p>
|
||||
It is up to you, the user, to double check your imports. This tool is just to get you close.
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<div id="dropZone" class="drop-zone rounded-lg p-8 text-center cursor-pointer">
|
||||
<div class="text-gray-500">
|
||||
<p class="text-lg mb-2">Drag and drop your Python file or ZIP archive here</p>
|
||||
<p class="text-sm mb-4">(or click to select)</p>
|
||||
<p class="text-xs">Maximum file size: 10MB</p>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="fileInput" class="hidden" accept=".py,.zip">
|
||||
</div>
|
||||
|
||||
<div id="resultsContainer" class="hidden bg-white rounded-lg shadow-md p-6">
|
||||
<h2 class="text-xl font-semibold text-gray-800 mb-4">Analysis Results</h2>
|
||||
<div id="loadingIndicator" class="hidden">
|
||||
<div class="flex items-center justify-center py-4">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="results" class="results-box font-mono text-sm bg-gray-50 rounded p-4 mb-4"></div>
|
||||
<button id="downloadBtn" class="hidden bg-blue-500 hover:bg-blue-600 text-white font-semibold py-2 px-4 rounded">
|
||||
Download requirements.txt
|
||||
</button> <br><br>
|
||||
<script async
|
||||
src="https://js.stripe.com/v3/buy-button.js">
|
||||
</script>
|
||||
|
||||
<stripe-buy-button
|
||||
buy-button-id="buy_btn_1QZkdjF7XLGd2a5gUp7oO0Xl"
|
||||
publishable-key="pk_live_51ODgd2F7XLGd2a5gWi8b4cBLoKrc2gr3nLHkvZolog1I0qcGadqsgDAhTAERs6ocR1UwebkhVsOQ7yn5YHdZ320r00dYH3oVr2"
|
||||
>
|
||||
</stripe-buy-button>
|
||||
</div>
|
||||
<div class="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h3 class="text-1xl font-bold text-gray-800 mb-2">Privacy or whatever</h3>
|
||||
<ul>
|
||||
<li>1. Although processing happens server-side, we don't keep your uploaded source code. It's stored in a temporary
|
||||
directory then cleaned up after. Might port this to be a WASM project to learn it, if it's possible to do so, so all processing happens
|
||||
on your device and I don't risk getting rate limited by pypi.org.
|
||||
</li>
|
||||
<br>
|
||||
<li>2. Only normal browser logs are kept in rotation to see if something is broken. You specificically are not tracked.</li>
|
||||
<li>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const dropZone = document.getElementById('dropZone');
|
||||
const fileInput = document.getElementById('fileInput');
|
||||
const resultsContainer = document.getElementById('resultsContainer');
|
||||
const loadingIndicator = document.getElementById('loadingIndicator');
|
||||
const results = document.getElementById('results');
|
||||
const downloadBtn = document.getElementById('downloadBtn');
|
||||
|
||||
let currentResults = null;
|
||||
|
||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, preventDefaults, false);
|
||||
});
|
||||
|
||||
function preventDefaults(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
['dragenter', 'dragover'].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, () => {
|
||||
dropZone.classList.add('dragover');
|
||||
});
|
||||
});
|
||||
|
||||
['dragleave', 'drop'].forEach(eventName => {
|
||||
dropZone.addEventListener(eventName, () => {
|
||||
dropZone.classList.remove('dragover');
|
||||
});
|
||||
});
|
||||
|
||||
dropZone.addEventListener('drop', handleDrop);
|
||||
dropZone.addEventListener('click', () => fileInput.click());
|
||||
fileInput.addEventListener('change', handleFileSelect);
|
||||
|
||||
function handleDrop(e) {
|
||||
const dt = e.dataTransfer;
|
||||
const file = dt.files[0];
|
||||
handleFile(file);
|
||||
}
|
||||
|
||||
function handleFileSelect(e) {
|
||||
const file = e.target.files[0];
|
||||
handleFile(file);
|
||||
}
|
||||
|
||||
function handleFile(file) {
|
||||
if (!file) return;
|
||||
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||
if (file.size > maxSize) {
|
||||
alert('File size exceeds 10MB limit');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!file.name.endsWith('.py') && !file.name.endsWith('.zip')) {
|
||||
alert('Please upload a Python file (.py) or ZIP archive');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
resultsContainer.classList.remove('hidden');
|
||||
loadingIndicator.classList.remove('hidden');
|
||||
results.innerHTML = '';
|
||||
downloadBtn.classList.add('hidden');
|
||||
|
||||
fetch('/analyze', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Last-Modified-Date': new Date(file.lastModified).toUTCString()
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
loadingIndicator.classList.add('hidden');
|
||||
|
||||
if (data.error) {
|
||||
results.innerHTML = `<div class="text-red-500">${data.error}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
currentResults = data;
|
||||
let output = '';
|
||||
|
||||
if (data.interpreter_version){
|
||||
console.log(data.interpreter_version)
|
||||
output += `<div class="mb-4"><strong>Vermin reported min Python Version: ${data.interpreter_version}</strong><br>`
|
||||
output += '</div>';
|
||||
}
|
||||
|
||||
if (data.file_list && data.file_list.length > 0) {
|
||||
output += '<div class="mb-4"><strong>File List:</strong><br>';
|
||||
data.file_list.forEach(fi => {
|
||||
output += `${fi}<br>`;
|
||||
});
|
||||
output += '</div>';
|
||||
}
|
||||
|
||||
if (data.requirements && data.requirements.length > 0) {
|
||||
output += '<div class="mb-4"><strong>Requirements:</strong><br>';
|
||||
data.requirements.forEach(req => {
|
||||
output += `${req}<br>`;
|
||||
});
|
||||
output += '</div>';
|
||||
}
|
||||
|
||||
if (data.unresolved && data.unresolved.length > 0) {
|
||||
output += '<div class="text-yellow-600"><strong>Unresolved Imports:</strong><br>';
|
||||
data.unresolved.forEach(imp => {
|
||||
output += `${imp}<br>`;
|
||||
});
|
||||
output += '</div>';
|
||||
}
|
||||
|
||||
if (data.reference_date) {
|
||||
output += `<div class="text-gray-500 mt-4">Reference Date: ${new Date(data.reference_date).toLocaleString()}</div>`;
|
||||
}
|
||||
|
||||
results.innerHTML = output;
|
||||
downloadBtn.classList.remove('hidden');
|
||||
})
|
||||
.catch(error => {
|
||||
loadingIndicator.classList.add('hidden');
|
||||
results.innerHTML = `<div class="text-red-500">Error: ${error.message}</div>`;
|
||||
});
|
||||
}
|
||||
|
||||
downloadBtn.addEventListener('click', () => {
|
||||
if (!currentResults) return;
|
||||
|
||||
fetch('/download', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(currentResults)
|
||||
})
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'requirements.txt';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error downloading file: ' + error.message);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user