pyservx 1.0.4__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyservx-1.0.4 → pyservx-1.1.0}/LICENSE +0 -0
- {pyservx-1.0.4 → pyservx-1.1.0}/MANIFEST.in +0 -0
- pyservx-1.1.0/PKG-INFO +74 -0
- pyservx-1.1.0/README.md +58 -0
- {pyservx-1.0.4 → pyservx-1.1.0}/pyproject.toml +31 -25
- {pyservx-1.0.4 → pyservx-1.1.0}/pyservx/__init__.py +0 -0
- pyservx-1.1.0/pyservx/file_operations.py +52 -0
- pyservx-1.1.0/pyservx/html_generator.py +443 -0
- pyservx-1.1.0/pyservx/request_handler.py +146 -0
- pyservx-1.1.0/pyservx/server.py +160 -0
- pyservx-1.1.0/pyservx.egg-info/PKG-INFO +74 -0
- {pyservx-1.0.4 → pyservx-1.1.0}/pyservx.egg-info/SOURCES.txt +5 -0
- {pyservx-1.0.4 → pyservx-1.1.0}/pyservx.egg-info/dependency_links.txt +0 -0
- {pyservx-1.0.4 → pyservx-1.1.0}/pyservx.egg-info/entry_points.txt +0 -0
- pyservx-1.1.0/pyservx.egg-info/requires.txt +1 -0
- {pyservx-1.0.4 → pyservx-1.1.0}/pyservx.egg-info/top_level.txt +0 -0
- pyservx-1.1.0/run.py +15 -0
- {pyservx-1.0.4 → pyservx-1.1.0}/setup.cfg +4 -4
- {pyservx-1.0.4 → pyservx-1.1.0}/tests/test_server.py +0 -0
- pyservx-1.0.4/PKG-INFO +0 -72
- pyservx-1.0.4/README.md +0 -48
- pyservx-1.0.4/pyservx/server.py +0 -639
- pyservx-1.0.4/pyservx.egg-info/PKG-INFO +0 -72
|
File without changes
|
|
File without changes
|
pyservx-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyservx
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: A simple Python server package for file sharing with a retro UI.
|
|
5
|
+
Author-email: Subz3r0x01 <subz3r0x01@protonmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/pyservx
|
|
8
|
+
Project-URL: Repository, https://github.com/yourusername/pyservx
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.6
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: qrcode
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# PyServeX
|
|
18
|
+
|
|
19
|
+
A simple HTTP server for file sharing with a retro-styled web interface.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Install using pip:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install pyservx
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or use pipx for an isolated environment (recommended for Linux):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pipx install pyservx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Ensure you have Python 3.6 or higher installed.
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Run the server:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python3 run.py
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- Follow the prompt to select a shared folder.
|
|
46
|
+
- The server will start at `http://localhost:8088` and other network IPs.
|
|
47
|
+
- Access the web interface to browse, download, or upload files.
|
|
48
|
+
- Use `Ctrl+C` to stop the server.
|
|
49
|
+
|
|
50
|
+
## Features
|
|
51
|
+
|
|
52
|
+
- File and folder browsing with a retro "hacker" UI.
|
|
53
|
+
- Download entire folders as ZIP files.
|
|
54
|
+
- Upload files via the web interface.
|
|
55
|
+
- Accessible via localhost (`127.0.0.1`) and network IPs.
|
|
56
|
+
- **QR Code for Easy Access:** Scan a QR code in the terminal to quickly access the server from your mobile device on the same network.
|
|
57
|
+
- **Real-time Progress Bars:** Enjoy real-time progress updates for both uploads and downloads, including ETA, transfer speed, and file size.
|
|
58
|
+
- **Multiple File Uploads:** Upload multiple files simultaneously through the web interface.
|
|
59
|
+
- **No File Size Restriction:** Upload files of any size without limitations.
|
|
60
|
+
- **Enhanced Web Interface:**
|
|
61
|
+
- **Search Functionality:** Quickly find files with the new search bar.
|
|
62
|
+
- **File Sorting:** Sort files by name, size, or date for better organization.
|
|
63
|
+
- **Modern & Responsive UI:** A refreshed, responsive design for seamless use across all devices.
|
|
64
|
+
- **Automated `robots.txt`:** A `robots.txt` file is automatically generated to prevent search engines from indexing your file server, enhancing privacy.
|
|
65
|
+
- **Modular Codebase:** The `server.py` file has been refactored into smaller, more maintainable modules (`request_handler.py`, `html_generator.py`, `file_operations.py`).
|
|
66
|
+
|
|
67
|
+
## Requirements
|
|
68
|
+
|
|
69
|
+
- Python 3.6+
|
|
70
|
+
- `qrcode` library (automatically installed with pip)
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT License
|
pyservx-1.1.0/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# PyServeX
|
|
2
|
+
|
|
3
|
+
A simple HTTP server for file sharing with a retro-styled web interface.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Install using pip:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install pyservx
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or use pipx for an isolated environment (recommended for Linux):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pipx install pyservx
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Ensure you have Python 3.6 or higher installed.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
Run the server:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
python3 run.py
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Follow the prompt to select a shared folder.
|
|
30
|
+
- The server will start at `http://localhost:8088` and other network IPs.
|
|
31
|
+
- Access the web interface to browse, download, or upload files.
|
|
32
|
+
- Use `Ctrl+C` to stop the server.
|
|
33
|
+
|
|
34
|
+
## Features
|
|
35
|
+
|
|
36
|
+
- File and folder browsing with a retro "hacker" UI.
|
|
37
|
+
- Download entire folders as ZIP files.
|
|
38
|
+
- Upload files via the web interface.
|
|
39
|
+
- Accessible via localhost (`127.0.0.1`) and network IPs.
|
|
40
|
+
- **QR Code for Easy Access:** Scan a QR code in the terminal to quickly access the server from your mobile device on the same network.
|
|
41
|
+
- **Real-time Progress Bars:** Enjoy real-time progress updates for both uploads and downloads, including ETA, transfer speed, and file size.
|
|
42
|
+
- **Multiple File Uploads:** Upload multiple files simultaneously through the web interface.
|
|
43
|
+
- **No File Size Restriction:** Upload files of any size without limitations.
|
|
44
|
+
- **Enhanced Web Interface:**
|
|
45
|
+
- **Search Functionality:** Quickly find files with the new search bar.
|
|
46
|
+
- **File Sorting:** Sort files by name, size, or date for better organization.
|
|
47
|
+
- **Modern & Responsive UI:** A refreshed, responsive design for seamless use across all devices.
|
|
48
|
+
- **Automated `robots.txt`:** A `robots.txt` file is automatically generated to prevent search engines from indexing your file server, enhancing privacy.
|
|
49
|
+
- **Modular Codebase:** The `server.py` file has been refactored into smaller, more maintainable modules (`request_handler.py`, `html_generator.py`, `file_operations.py`).
|
|
50
|
+
|
|
51
|
+
## Requirements
|
|
52
|
+
|
|
53
|
+
- Python 3.6+
|
|
54
|
+
- `qrcode` library (automatically installed with pip)
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
MIT License
|
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
[project]
|
|
2
|
-
name = "pyservx"
|
|
3
|
-
version = "1.
|
|
4
|
-
description = "A simple Python server package"
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.6"
|
|
7
|
-
license =
|
|
8
|
-
authors = [
|
|
9
|
-
{ name = "
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
[
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
[
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyservx"
|
|
3
|
+
version = "1.1.0"
|
|
4
|
+
description = "A simple Python server package for file sharing with a retro UI."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.6"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Subz3r0x01", email = "subz3r0x01@protonmail.com" }
|
|
10
|
+
]
|
|
11
|
+
dependencies = [
|
|
12
|
+
"qrcode"
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
[build-system]
|
|
21
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
22
|
+
build-backend = "setuptools.build_meta"
|
|
23
|
+
|
|
24
|
+
[tool.setuptools.packages]
|
|
25
|
+
find = { include = ["pyservx*"] }
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/yourusername/pyservx"
|
|
29
|
+
Repository = "https://github.com/yourusername/pyservx"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
26
32
|
pyservx = "pyservx.server:main"
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import zipfile
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
|
|
7
|
+
def format_size(size):
|
|
8
|
+
if size < 1024:
|
|
9
|
+
return f"{size} B"
|
|
10
|
+
elif size < 1024**2:
|
|
11
|
+
return f"{size / 1024:.2f} KB"
|
|
12
|
+
elif size < 1024**3:
|
|
13
|
+
return f"{size / (1024**2):.2f} MB"
|
|
14
|
+
else:
|
|
15
|
+
return f"{size / (1024**3):.2f} GB"
|
|
16
|
+
|
|
17
|
+
def zip_folder(folder_path):
|
|
18
|
+
memory_file = BytesIO()
|
|
19
|
+
with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
|
20
|
+
for root, _, files in os.walk(folder_path):
|
|
21
|
+
for file in files:
|
|
22
|
+
abs_path = os.path.join(root, file)
|
|
23
|
+
rel_path = os.path.relpath(abs_path, folder_path)
|
|
24
|
+
zipf.write(abs_path, rel_path)
|
|
25
|
+
memory_file.seek(0)
|
|
26
|
+
return memory_file
|
|
27
|
+
|
|
28
|
+
def write_file_in_chunks(file_path, file_content, progress_callback=None):
|
|
29
|
+
total_size = len(file_content)
|
|
30
|
+
bytes_written = 0
|
|
31
|
+
chunk_size = 8192 # 8KB chunks
|
|
32
|
+
|
|
33
|
+
with open(file_path, 'wb') as f:
|
|
34
|
+
for i in range(0, total_size, chunk_size):
|
|
35
|
+
chunk = file_content[i:i + chunk_size]
|
|
36
|
+
f.write(chunk)
|
|
37
|
+
bytes_written += len(chunk)
|
|
38
|
+
if progress_callback:
|
|
39
|
+
progress_callback(bytes_written, total_size)
|
|
40
|
+
|
|
41
|
+
def read_file_in_chunks(file_path, chunk_size=8192, progress_callback=None):
|
|
42
|
+
file_size = os.path.getsize(file_path)
|
|
43
|
+
bytes_read = 0
|
|
44
|
+
with open(file_path, 'rb') as f:
|
|
45
|
+
while True:
|
|
46
|
+
chunk = f.read(chunk_size)
|
|
47
|
+
if not chunk:
|
|
48
|
+
break
|
|
49
|
+
bytes_read += len(chunk)
|
|
50
|
+
if progress_callback:
|
|
51
|
+
progress_callback(bytes_read, file_size)
|
|
52
|
+
yield chunk
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import html
|
|
4
|
+
import os
|
|
5
|
+
import urllib.parse
|
|
6
|
+
import datetime
|
|
7
|
+
|
|
8
|
+
def format_size(size):
|
|
9
|
+
if size < 1024:
|
|
10
|
+
return f"{size} B"
|
|
11
|
+
elif size < 1024**2:
|
|
12
|
+
return f"{size / 1024:.2f} KB"
|
|
13
|
+
elif size < 1024**3:
|
|
14
|
+
return f"{size / (1024**2):.2f} MB"
|
|
15
|
+
else:
|
|
16
|
+
return f"{size / (1024**3):.2f} GB"
|
|
17
|
+
|
|
18
|
+
def list_directory_page(handler, path):
|
|
19
|
+
try:
|
|
20
|
+
entries = os.listdir(path)
|
|
21
|
+
except OSError:
|
|
22
|
+
handler.send_error(404, "Cannot list directory")
|
|
23
|
+
return None
|
|
24
|
+
|
|
25
|
+
query_params = urllib.parse.parse_qs(urllib.parse.urlparse(handler.path).query)
|
|
26
|
+
search_query = query_params.get('q', [''])[0]
|
|
27
|
+
sort_by = query_params.get('sort', ['name'])[0]
|
|
28
|
+
sort_order = query_params.get('order', ['asc'])[0]
|
|
29
|
+
|
|
30
|
+
if search_query:
|
|
31
|
+
entries = [e for e in entries if search_query.lower() in e.lower()]
|
|
32
|
+
|
|
33
|
+
def sort_key(item):
|
|
34
|
+
item_path = os.path.join(path, item)
|
|
35
|
+
if os.path.isdir(item_path):
|
|
36
|
+
return (0, item.lower()) # Directories first
|
|
37
|
+
if sort_by == 'size':
|
|
38
|
+
return (1, os.path.getsize(item_path))
|
|
39
|
+
elif sort_by == 'date':
|
|
40
|
+
return (1, os.path.getmtime(item_path))
|
|
41
|
+
else:
|
|
42
|
+
return (1, item.lower())
|
|
43
|
+
|
|
44
|
+
entries.sort(key=sort_key, reverse=sort_order == 'desc')
|
|
45
|
+
|
|
46
|
+
displaypath = html.escape(urllib.parse.unquote(handler.path))
|
|
47
|
+
|
|
48
|
+
# Build list items for directories and files
|
|
49
|
+
list_rows = []
|
|
50
|
+
# Parent directory link if not root
|
|
51
|
+
if handler.path != '/':
|
|
52
|
+
parent = os.path.dirname(handler.path.rstrip('/'))
|
|
53
|
+
if not parent.endswith('/'):
|
|
54
|
+
parent += '/'
|
|
55
|
+
list_rows.append(f"""
|
|
56
|
+
<tr class="hover:bg-green-900/20">
|
|
57
|
+
<td class="py-2 px-4 border-b border-green-700/50"><a href="{html.escape(parent)}" class="text-neon block">.. (Parent Directory)</a></td>
|
|
58
|
+
<td class="py-2 px-4 border-b border-green-700/50 text-right">-</td>
|
|
59
|
+
<td class="py-2 px-4 border-b border-green-700/50 text-right">-</td>
|
|
60
|
+
</tr>
|
|
61
|
+
""")
|
|
62
|
+
|
|
63
|
+
for name in entries:
|
|
64
|
+
fullpath = os.path.join(path, name)
|
|
65
|
+
displayname = name + '/' if os.path.isdir(fullpath) else name
|
|
66
|
+
href = urllib.parse.quote(name)
|
|
67
|
+
if os.path.isdir(fullpath):
|
|
68
|
+
href += '/'
|
|
69
|
+
|
|
70
|
+
size = "-"
|
|
71
|
+
date_modified = "-"
|
|
72
|
+
if os.path.isfile(fullpath):
|
|
73
|
+
size = format_size(os.path.getsize(fullpath))
|
|
74
|
+
date_modified = datetime.datetime.fromtimestamp(os.path.getmtime(fullpath)).strftime('%Y-%m-%d %H:%M:%S')
|
|
75
|
+
|
|
76
|
+
# Add download folder zip link for directories
|
|
77
|
+
if os.path.isdir(fullpath):
|
|
78
|
+
list_rows.append(
|
|
79
|
+
f"""
|
|
80
|
+
<tr class="hover:bg-green-900/20">
|
|
81
|
+
<td class="py-2 px-4 border-b border-green-700/50">
|
|
82
|
+
<a href="{href}" class="text-neon block">{html.escape(displayname)}</a>
|
|
83
|
+
</td>
|
|
84
|
+
<td class="py-2 px-4 border-b border-green-700/50 text-right">{size}</td>
|
|
85
|
+
<td class="py-2 px-4 border-b border-green-700/50 text-right">{date_modified}</td>
|
|
86
|
+
</tr>
|
|
87
|
+
<tr class="hover:bg-green-900/20">
|
|
88
|
+
<td class="py-2 px-4 border-b border-green-700/50" colspan="3">
|
|
89
|
+
<a href="{href}download_folder" class="text-neon block">📦 Zip Download</a>
|
|
90
|
+
</td>
|
|
91
|
+
</tr>
|
|
92
|
+
"""
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
list_rows.append(f"""
|
|
96
|
+
<tr class="hover:bg-green-900/20">
|
|
97
|
+
<td class="py-2 px-4 border-b border-green-700/50"><a href="{href}" class="text-neon block">{html.escape(displayname)}</a></td>
|
|
98
|
+
<td class="py-2 px-4 border-b border-green-700/50 text-right">{size}</td>
|
|
99
|
+
<td class="py-2 px-4 border-b border-green-700/50 text-right">{date_modified}</td>
|
|
100
|
+
</tr>
|
|
101
|
+
""")
|
|
102
|
+
|
|
103
|
+
list_html = '\n'.join(list_rows)
|
|
104
|
+
|
|
105
|
+
return f"""<!DOCTYPE html>
|
|
106
|
+
<html lang="en">
|
|
107
|
+
<head>
|
|
108
|
+
<meta charset="UTF-8" />
|
|
109
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
110
|
+
<title>PyServeX - Index of {displaypath}</title>
|
|
111
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
112
|
+
<style>
|
|
113
|
+
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
|
|
114
|
+
|
|
115
|
+
body {{
|
|
116
|
+
font-family: 'VT323', monospace;
|
|
117
|
+
background: #000000;
|
|
118
|
+
min-height: 100vh;
|
|
119
|
+
margin: 0;
|
|
120
|
+
overflow-x: hidden;
|
|
121
|
+
}}
|
|
122
|
+
|
|
123
|
+
.text-neon {{
|
|
124
|
+
color: #00ff00;
|
|
125
|
+
}}
|
|
126
|
+
|
|
127
|
+
.typewriter h1 {{
|
|
128
|
+
overflow: hidden;
|
|
129
|
+
white-space: nowrap;
|
|
130
|
+
animation: typing 3s steps(40, end), blink-caret 0.5s step-end infinite;
|
|
131
|
+
margin: 0 auto;
|
|
132
|
+
text-align: center;
|
|
133
|
+
}}
|
|
134
|
+
|
|
135
|
+
@keyframes typing {{
|
|
136
|
+
from {{ width: 0; }}
|
|
137
|
+
to {{ width: 100%; }}
|
|
138
|
+
}}
|
|
139
|
+
|
|
140
|
+
@keyframes blink-caret {{
|
|
141
|
+
from, to {{ border-right: 2px solid #00ff00; }}
|
|
142
|
+
50% {{ border-right: 2px solid transparent; }}
|
|
143
|
+
}}
|
|
144
|
+
|
|
145
|
+
.glitch {{
|
|
146
|
+
position: relative;
|
|
147
|
+
animation: glitch 2s infinite;
|
|
148
|
+
}}
|
|
149
|
+
|
|
150
|
+
@keyframes glitch {{
|
|
151
|
+
0% {{ transform: translate(0); }}
|
|
152
|
+
10% {{ transform: translate(-2px, 2px); }}
|
|
153
|
+
20% {{ transform: translate(2px, -2px); }}
|
|
154
|
+
30% {{ transform: translate(-2px, 2px); }}
|
|
155
|
+
40% {{ transform: translate(0); }}
|
|
156
|
+
100% {{ transform: translate(0); }}
|
|
157
|
+
}}
|
|
158
|
+
|
|
159
|
+
.scanline {{
|
|
160
|
+
position: absolute;
|
|
161
|
+
top: 0;
|
|
162
|
+
left: 0;
|
|
163
|
+
width: 100%;
|
|
164
|
+
height: 100%;
|
|
165
|
+
background: linear-gradient(
|
|
166
|
+
to bottom,
|
|
167
|
+
rgba(255, 255, 255, 0),
|
|
168
|
+
rgba(255, 255, 255, 0.1) 50%,
|
|
169
|
+
rgba(255, 255, 255, 0)
|
|
170
|
+
);
|
|
171
|
+
animation: scan 4s linear infinite;
|
|
172
|
+
pointer-events: none;
|
|
173
|
+
}}
|
|
174
|
+
|
|
175
|
+
@keyframes scan {{
|
|
176
|
+
0% {{ transform: translateY(-100%); }}
|
|
177
|
+
100% {{ transform: translateY(100%); }}
|
|
178
|
+
}}
|
|
179
|
+
|
|
180
|
+
.particle {{
|
|
181
|
+
position: absolute;
|
|
182
|
+
width: 3px;
|
|
183
|
+
height: 3px;
|
|
184
|
+
background: #00ff00;
|
|
185
|
+
opacity: 0.5;
|
|
186
|
+
animation: flicker 3s infinite;
|
|
187
|
+
}}
|
|
188
|
+
|
|
189
|
+
@keyframes flicker {{
|
|
190
|
+
0% {{ opacity: 0.5; }}
|
|
191
|
+
50% {{ opacity: 0.1; }}
|
|
192
|
+
100% {{ opacity: 0.5; }}
|
|
193
|
+
}}
|
|
194
|
+
|
|
195
|
+
main {{
|
|
196
|
+
margin-top: 100px; /* Adjust based on header height */
|
|
197
|
+
padding: 2rem;
|
|
198
|
+
color: #00ff00;
|
|
199
|
+
text-align: left;
|
|
200
|
+
max-width: 900px;
|
|
201
|
+
margin-left: auto;
|
|
202
|
+
margin-right: auto;
|
|
203
|
+
}}
|
|
204
|
+
|
|
205
|
+
ul {{
|
|
206
|
+
list-style-type: none;
|
|
207
|
+
padding-left: 0;
|
|
208
|
+
}}
|
|
209
|
+
|
|
210
|
+
li {{
|
|
211
|
+
margin-bottom: 0.7rem;
|
|
212
|
+
font-size: 1.2rem;
|
|
213
|
+
}}
|
|
214
|
+
|
|
215
|
+
a {{
|
|
216
|
+
text-decoration: none;
|
|
217
|
+
}}
|
|
218
|
+
|
|
219
|
+
a:hover {{
|
|
220
|
+
text-decoration: underline;
|
|
221
|
+
}}
|
|
222
|
+
|
|
223
|
+
.upload-form, .search-form {{
|
|
224
|
+
margin-top: 1.5rem;
|
|
225
|
+
padding: 1rem;
|
|
226
|
+
border: 1px solid #00ff00;
|
|
227
|
+
border-radius: 5px;
|
|
228
|
+
}}
|
|
229
|
+
|
|
230
|
+
.upload-form label, .search-form label {{
|
|
231
|
+
display: block;
|
|
232
|
+
margin-bottom: 0.5rem;
|
|
233
|
+
}}
|
|
234
|
+
|
|
235
|
+
.upload-form input[type="file"], .search-form input[type="text"] {{
|
|
236
|
+
color: #00ff00;
|
|
237
|
+
background: #000000;
|
|
238
|
+
border: 1px solid #00ff00;
|
|
239
|
+
padding: 0.5rem;
|
|
240
|
+
}}
|
|
241
|
+
|
|
242
|
+
.upload-form button, .search-form button {{
|
|
243
|
+
background: #00ff00;
|
|
244
|
+
color: #000000;
|
|
245
|
+
padding: 0.5rem 1rem;
|
|
246
|
+
border: none;
|
|
247
|
+
cursor: pointer;
|
|
248
|
+
font-family: 'VT323', monospace;
|
|
249
|
+
font-size: 1.2rem;
|
|
250
|
+
}}
|
|
251
|
+
|
|
252
|
+
.upload-form button:hover, .search-form button:hover {{
|
|
253
|
+
background: #00cc00;
|
|
254
|
+
}}
|
|
255
|
+
|
|
256
|
+
table {{
|
|
257
|
+
width: 100%;
|
|
258
|
+
border-collapse: collapse;
|
|
259
|
+
margin-top: 1.5rem;
|
|
260
|
+
}}
|
|
261
|
+
|
|
262
|
+
th, td {{
|
|
263
|
+
text-align: left;
|
|
264
|
+
padding: 0.75rem;
|
|
265
|
+
border-bottom: 1px solid rgba(0, 255, 0, 0.3);
|
|
266
|
+
}}
|
|
267
|
+
|
|
268
|
+
th {{
|
|
269
|
+
background-color: rgba(0, 255, 0, 0.1);
|
|
270
|
+
color: #00ff00;
|
|
271
|
+
font-weight: normal;
|
|
272
|
+
cursor: pointer;
|
|
273
|
+
}}
|
|
274
|
+
|
|
275
|
+
th:hover {{
|
|
276
|
+
background-color: rgba(0, 255, 0, 0.2);
|
|
277
|
+
}}
|
|
278
|
+
|
|
279
|
+
tr:nth-child(even) {{
|
|
280
|
+
background-color: rgba(0, 255, 0, 0.05);
|
|
281
|
+
}}
|
|
282
|
+
|
|
283
|
+
/* Progress bar and popup styles */
|
|
284
|
+
#progressBarContainer {{
|
|
285
|
+
display: none;
|
|
286
|
+
margin-top: 1rem;
|
|
287
|
+
background-color: #333;
|
|
288
|
+
border: 1px solid #00ff00;
|
|
289
|
+
padding: 0.5rem;
|
|
290
|
+
border-radius: 5px;
|
|
291
|
+
}}
|
|
292
|
+
|
|
293
|
+
#progressBar {{
|
|
294
|
+
width: 0%;
|
|
295
|
+
height: 20px;
|
|
296
|
+
background-color: #00ff00;
|
|
297
|
+
text-align: center;
|
|
298
|
+
line-height: 20px;
|
|
299
|
+
color: #000;
|
|
300
|
+
font-size: 0.8rem;
|
|
301
|
+
}}
|
|
302
|
+
|
|
303
|
+
#progressText {{
|
|
304
|
+
color: #00ff00;
|
|
305
|
+
margin-top: 0.5rem;
|
|
306
|
+
font-size: 0.9rem;
|
|
307
|
+
}}
|
|
308
|
+
|
|
309
|
+
#successPopup {{
|
|
310
|
+
display: none;
|
|
311
|
+
position: fixed;
|
|
312
|
+
top: 20px;
|
|
313
|
+
left: 50%;
|
|
314
|
+
transform: translateX(-50%);
|
|
315
|
+
background-color: #00ff00;
|
|
316
|
+
color: #000;
|
|
317
|
+
padding: 1rem 2rem;
|
|
318
|
+
border-radius: 5px;
|
|
319
|
+
font-size: 1.2rem;
|
|
320
|
+
z-index: 1000;
|
|
321
|
+
box-shadow: 0 0 10px rgba(0, 255, 0, 0.5);
|
|
322
|
+
}}
|
|
323
|
+
</style>
|
|
324
|
+
</head>
|
|
325
|
+
<body>
|
|
326
|
+
<div class="scanline"></div>
|
|
327
|
+
<header>
|
|
328
|
+
<div class="text-center">
|
|
329
|
+
<h1 class="text-4xl md:text-6xl text-neon typewriter glitch">PyServeX</h1>
|
|
330
|
+
</div>
|
|
331
|
+
</header>
|
|
332
|
+
<main>
|
|
333
|
+
<h2 class="text-3xl mb-4 text-neon">Index of {displaypath}</h2>
|
|
334
|
+
<div class="search-form mb-6 p-4 border border-green-700/50 rounded-lg">
|
|
335
|
+
<form action="{html.escape(handler.path)}" method="GET" class="flex flex-col sm:flex-row items-center space-y-2 sm:space-y-0 sm:space-x-2">
|
|
336
|
+
<input type="text" name="q" placeholder="Search files..." value="{html.escape(search_query)}" class="flex-grow p-2 bg-black text-neon border border-green-700/50 rounded-md focus:outline-none focus:ring-1 focus:ring-green-500">
|
|
337
|
+
<button type="submit" class="bg-green-500 text-black py-2 px-4 rounded-md hover:bg-green-600 transition-colors duration-200">Search</button>
|
|
338
|
+
</form>
|
|
339
|
+
</div>
|
|
340
|
+
<div class="overflow-x-auto">
|
|
341
|
+
<table class="min-w-full bg-black border border-green-700/50 rounded-lg">
|
|
342
|
+
<thead>
|
|
343
|
+
<tr>
|
|
344
|
+
<th class="py-3 px-4 border-b border-green-700/50 text-left text-neon">
|
|
345
|
+
<a href="?sort=name&order={{'desc' if sort_by == 'name' and sort_order == 'asc' else 'asc'}}" class="block">Name</a>
|
|
346
|
+
</th>
|
|
347
|
+
<th class="py-3 px-4 border-b border-green-700/50 text-right text-neon">
|
|
348
|
+
<a href="?sort=size&order={{'desc' if sort_by == 'size' and sort_order == 'asc' else 'asc'}}" class="block">Size</a>
|
|
349
|
+
</th>
|
|
350
|
+
<th class="py-3 px-4 border-b border-green-700/50 text-right text-neon">
|
|
351
|
+
<a href="?sort=date&order={{'desc' if sort_by == 'date' and sort_order == 'asc' else 'asc'}}" class="block">Date Modified</a>
|
|
352
|
+
</th>
|
|
353
|
+
</tr>
|
|
354
|
+
</thead>
|
|
355
|
+
<tbody>
|
|
356
|
+
{list_html}
|
|
357
|
+
</tbody>
|
|
358
|
+
</table>
|
|
359
|
+
</div>
|
|
360
|
+
<div class="upload-form mt-6 p-4 border border-green-700/50 rounded-lg">
|
|
361
|
+
<form id="uploadForm" action="{html.escape(handler.path)}upload" method="POST" enctype="multipart/form-data" class="flex flex-col sm:flex-row items-center space-y-2 sm:space-y-0 sm:space-x-2">
|
|
362
|
+
<label for="file-upload" class="text-neon">Upload files:</label>
|
|
363
|
+
<input type="file" id="file-upload" name="file" multiple class="flex-grow p-2 bg-black text-neon border border-green-700/50 rounded-md focus:outline-none focus:ring-1 focus:ring-green-500" />
|
|
364
|
+
<button type="submit" class="bg-green-500 text-black py-2 px-4 rounded-md hover:bg-green-600 transition-colors duration-200">Upload</button>
|
|
365
|
+
</form>
|
|
366
|
+
<div id="progressBarContainer">
|
|
367
|
+
<div id="progressBar"></div>
|
|
368
|
+
<div id="progressText"></div>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
</main>
|
|
372
|
+
|
|
373
|
+
<div id="successPopup"></div>
|
|
374
|
+
|
|
375
|
+
<script>
|
|
376
|
+
// Generate random particles for hacker effect
|
|
377
|
+
function createParticles() {{
|
|
378
|
+
const numParticles = 30;
|
|
379
|
+
for (let i = 0; i < numParticles; i++) {{
|
|
380
|
+
const particle = document.createElement('div');
|
|
381
|
+
particle.classList.add('particle');
|
|
382
|
+
particle.style.left = `${{Math.random() * 100}}vw`;
|
|
383
|
+
particle.style.top = `${{Math.random() * 100}}vh`;
|
|
384
|
+
particle.style.animationDelay = `${{Math.random() * 3}}s`;
|
|
385
|
+
document.body.appendChild(particle);
|
|
386
|
+
}}
|
|
387
|
+
}}
|
|
388
|
+
|
|
389
|
+
// Handle file uploads with progress bar
|
|
390
|
+
document.getElementById('uploadForm').addEventListener('submit', function(e) {{
|
|
391
|
+
e.preventDefault();
|
|
392
|
+
|
|
393
|
+
const formData = new FormData(this);
|
|
394
|
+
const xhr = new XMLHttpRequest();
|
|
395
|
+
|
|
396
|
+
const progressBarContainer = document.getElementById('progressBarContainer');
|
|
397
|
+
const progressBar = document.getElementById('progressBar');
|
|
398
|
+
const progressText = document.getElementById('progressText');
|
|
399
|
+
const successPopup = document.getElementById('successPopup');
|
|
400
|
+
|
|
401
|
+
progressBarContainer.style.display = 'block';
|
|
402
|
+
progressBar.style.width = '0%';
|
|
403
|
+
progressText.textContent = 'Uploading...';
|
|
404
|
+
|
|
405
|
+
xhr.upload.addEventListener('progress', function(event) {{
|
|
406
|
+
if (event.lengthComputable) {{
|
|
407
|
+
const percent = (event.loaded / event.total) * 100;
|
|
408
|
+
progressBar.style.width = percent.toFixed(2) + '%';
|
|
409
|
+
progressText.textContent = `Uploading: ${{percent.toFixed(2)}}%`;
|
|
410
|
+
}}
|
|
411
|
+
}});
|
|
412
|
+
|
|
413
|
+
xhr.addEventListener('load', function() {{
|
|
414
|
+
progressBarContainer.style.display = 'none';
|
|
415
|
+
if (xhr.status === 200) {{
|
|
416
|
+
const response = JSON.parse(xhr.responseText);
|
|
417
|
+
successPopup.textContent = response.message;
|
|
418
|
+
successPopup.style.display = 'block';
|
|
419
|
+
setTimeout(() => {{
|
|
420
|
+
successPopup.style.display = 'none';
|
|
421
|
+
window.location.reload(); // Reload page to show new files
|
|
422
|
+
}}, 2000);
|
|
423
|
+
}} else {{
|
|
424
|
+
progressText.textContent = 'Upload failed!';
|
|
425
|
+
alert('Upload failed: ' + xhr.statusText);
|
|
426
|
+
}}
|
|
427
|
+
}});
|
|
428
|
+
|
|
429
|
+
xhr.addEventListener('error', function() {{
|
|
430
|
+
progressBarContainer.style.display = 'none';
|
|
431
|
+
progressText.textContent = 'Upload failed!';
|
|
432
|
+
alert('Upload failed due to a network error.');
|
|
433
|
+
}});
|
|
434
|
+
|
|
435
|
+
xhr.open('POST', this.action);
|
|
436
|
+
xhr.send(formData);
|
|
437
|
+
}});
|
|
438
|
+
|
|
439
|
+
window.onload = createParticles;
|
|
440
|
+
</script>
|
|
441
|
+
</body>
|
|
442
|
+
</html>
|
|
443
|
+
"""
|