UPdate.
This commit is contained in:
parent
dd108c2004
commit
f0591d4939
src/snek
@ -33,6 +33,7 @@ from snek.view.about import AboutHTMLView, AboutMDView
|
||||
from snek.view.avatar import AvatarView
|
||||
from snek.view.docs import DocsHTMLView, DocsMDView
|
||||
from snek.view.drive import DriveView
|
||||
from snek.view.drive import DriveApiView
|
||||
from snek.view.index import IndexView
|
||||
from snek.view.login import LoginView
|
||||
from snek.view.logout import LogoutView
|
||||
@ -178,7 +179,8 @@ class Application(BaseApplication):
|
||||
self.router.add_view("/threads.html", ThreadsView)
|
||||
self.router.add_view("/terminal.ws", TerminalSocketView)
|
||||
self.router.add_view("/terminal.html", TerminalView)
|
||||
self.router.add_view("/drive.json", DriveView)
|
||||
self.router.add_view("/drive.json", DriveApiView)
|
||||
self.router.add_view("/drive.html", DriveView)
|
||||
self.router.add_view("/drive/{drive}.json", DriveView)
|
||||
self.router.add_view("/stats.json", StatsView)
|
||||
self.router.add_view("/user/{user}.html", UserView)
|
||||
|
@ -9,6 +9,7 @@ class FileBrowser extends HTMLElement {
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.path = this.getAttribute("path") || "";
|
||||
this.renderShell();
|
||||
this.load();
|
||||
}
|
||||
@ -19,11 +20,11 @@ class FileBrowser extends HTMLElement {
|
||||
<style>
|
||||
:host { display:block; font-family: system-ui, sans-serif; box-sizing: border-box; }
|
||||
nav { display:flex; flex-wrap:wrap; gap:.5rem; margin:.5rem 0; align-items:center; }
|
||||
button { padding:.35rem .65rem; border:none; border-radius:4px; background:#0074d9; color:#fff; cursor:pointer; font:inherit; }
|
||||
button { padding:.35rem .65rem; border:none; border-radius:4px; background:#f05a28; color:#fff; cursor:pointer; font:inherit; }
|
||||
button:disabled { background:#999; cursor:not-allowed; }
|
||||
.crumb { font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:1rem; }
|
||||
.tile { border:1px solid #ddd; border-radius:8px; padding:.5rem; background:#fafafa; text-align:center; cursor:pointer; transition:box-shadow .2s ease; }
|
||||
.tile { border:1px solid #f05a28; border-radius:8px; padding:.5rem; background:#000000; text-align:center; cursor:pointer; transition:box-shadow .2s ease; }
|
||||
.tile:hover { box-shadow:0 2px 8px rgba(0,0,0,.1); }
|
||||
img.thumb { width:100%; height:90px; object-fit:cover; border-radius:6px; }
|
||||
.icon { font-size:48px; line-height:90px; }
|
||||
|
@ -35,6 +35,7 @@
|
||||
<div class="logo no-select">{% block header_text %}{% endblock %}</div>
|
||||
<nav class="no-select" style="overflow:hidden;scroll-behavior:smooth">
|
||||
<a class="no-select" href="/web.html">🏠</a>
|
||||
<a class="no-select" href="/drive.html">📂</a>
|
||||
<a class="no-select" href="/search-user.html">🔍</a>
|
||||
<a class="no-select" style="display:none" id="install-button" href="#">📥</a>
|
||||
<a class="no-select" href="/threads.html">👥</a>
|
||||
|
9
src/snek/templates/drive.html
Normal file
9
src/snek/templates/drive.html
Normal file
@ -0,0 +1,9 @@
|
||||
{% extends "app.html" %}
|
||||
|
||||
{% block header_text %}Drive{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="container">
|
||||
<file-manager path="{{path}}" style="flex: 1"></file-manager>
|
||||
</div>
|
||||
{% endblock %}
|
@ -3,43 +3,7 @@
|
||||
{% block header_text %}<h1><i class="fa-solid fa-plus"></i> Create Repository</h1>{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<style>
|
||||
.container {
|
||||
div,input,label,button{
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
}
|
||||
form {
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
label { font-weight: bold; display: flex; align-items: center; gap: 0.5rem;}
|
||||
input[type="text"] {
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ccc; border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
button, a.button {
|
||||
background: #198754; color: #fff; border: none; border-radius: 5px;
|
||||
padding: 0.1rem 0.8rem; text-decoration: none; cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
font-size: 1rem; display: inline-flex; align-items: center; gap: 0.4rem;
|
||||
}
|
||||
.
|
||||
.cancel {
|
||||
background: #6c757d;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.container { max-width: 98vw; }
|
||||
form { padding: 1rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% include 'settings/repositories/form.html' %}
|
||||
<div class="container">
|
||||
<form action="/settings/repositories/create.html" method="post">
|
||||
<div>
|
||||
@ -52,8 +16,9 @@ button, a.button {
|
||||
<i class="fa-solid fa-lock"></i> Private
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit"><i class="fa-solid fa-plus"></i> Create</button>
|
||||
<button onclick="history.back()" class="cancel"><i class="fa-solid fa-arrow-left"></i> Back</button>
|
||||
<button type="submit"><i class="fa-solid fa-pen"></i> Update</button>
|
||||
<button onclick="history.back()" class="cancel"><i class="fa-solid fa-arrow-left"></i>Cancel</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -3,33 +3,8 @@
|
||||
{% block header_text %}<h1><i class="fa-solid fa-trash-can"></i> Delete Repository</h1>{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<style>
|
||||
.repo-name {
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
margin: 1rem 0;
|
||||
color: #dc3545;
|
||||
}
|
||||
.actions {
|
||||
display: flex; gap: 1rem; justify-content: left; margin-top: 1.5rem;
|
||||
}
|
||||
button {
|
||||
background: #dc3545; color: #fff;
|
||||
border: none; border-radius: 5px; padding: 0.6rem 1.2rem;
|
||||
font-size: 1rem; cursor: pointer;
|
||||
display: flex; align-items: center; gap: 0.5rem; text-decoration: none; justify-content: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.cancel {
|
||||
background: #6c757d;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.container { max-width: 98vw; }
|
||||
.confirm-box { padding: 1rem; }
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
{% include "settings/repositories/form.html" %}
|
||||
<div class="container">
|
||||
<p>Are you sure you want to <strong>delete</strong> the following repository?</p>
|
||||
<div class="repo-name"><i class="fa-solid fa-book"></i> {{ repository.name }}</div>
|
||||
<form method="post" style="margin-top:1.5rem;">
|
||||
|
28
src/snek/templates/settings/repositories/form.html
Normal file
28
src/snek/templates/settings/repositories/form.html
Normal file
@ -0,0 +1,28 @@
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<style>
|
||||
form {
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
div {
|
||||
padding: 10px;
|
||||
padding-bottom: 15px
|
||||
}
|
||||
}
|
||||
label { font-weight: bold; display: flex; align-items: center; gap: 0.5rem;}
|
||||
button {
|
||||
background: #0d6efd; color: #fff;
|
||||
border: none; border-radius: 5px; padding: 0.6rem 1rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem; display: inline-flex; align-items: center; gap: 0.4rem;
|
||||
}
|
||||
.cancel {
|
||||
background: #6c757d;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.container { max-width: 98vw; }
|
||||
form { padding: 1rem; }
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
@ -3,28 +3,8 @@
|
||||
{% block header_text %}<h1><i class="fa-solid fa-pen"></i> Update Repository</h1>{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
|
||||
<style>
|
||||
form {
|
||||
padding: 2rem;
|
||||
border-radius: 10px;
|
||||
}
|
||||
label { font-weight: bold; display: flex; align-items: center; gap: 0.5rem;}
|
||||
button {
|
||||
background: #0d6efd; color: #fff;
|
||||
border: none; border-radius: 5px; padding: 0.6rem 1rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem; display: inline-flex; align-items: center; gap: 0.4rem;
|
||||
}
|
||||
.cancel {
|
||||
background: #6c757d;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
.container { max-width: 98vw; }
|
||||
form { padding: 1rem; }
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
{% include "settings/repositories/form.html" %}
|
||||
<div class="container">
|
||||
<form method="post">
|
||||
<!-- Assume hidden id for backend use -->
|
||||
<input type="hidden" name="id" value="{{ repository.id }}">
|
||||
|
@ -11,47 +11,42 @@ from datetime import datetime
|
||||
|
||||
|
||||
|
||||
"""Run with: python server.py (Python ≥ 3.9)
|
||||
Visit http://localhost:8080 to try the demo.
|
||||
"""
|
||||
from aiohttp import web
|
||||
from pathlib import Path
|
||||
import mimetypes, urllib.parse
|
||||
|
||||
# ---------- Configuration --------------------------------------------------
|
||||
BASE_DIR = Path(__file__).parent.resolve()
|
||||
ROOT_DIR = (BASE_DIR / "storage").resolve() # files shown to the outside world
|
||||
ASSETS_DIR = (BASE_DIR / "assets").resolve() # JS & demo HTML
|
||||
ROOT_DIR.mkdir(exist_ok=True)
|
||||
ASSETS_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# ---------- Helpers --------------------------------------------------------
|
||||
|
||||
def safe_resolve_path(rel: str) -> Path:
|
||||
"""Return *absolute* path inside ROOT_DIR or raise FileNotFoundError."""
|
||||
target = (ROOT_DIR / rel.lstrip("/")).resolve()
|
||||
if target == ROOT_DIR or ROOT_DIR in target.parents:
|
||||
return target
|
||||
raise FileNotFoundError("Unsafe path")
|
||||
|
||||
# ---------- API view -------------------------------------------------------
|
||||
|
||||
class DriveView(BaseView):
|
||||
|
||||
async def get(self):
|
||||
target = await self.services.user.get_home_folder(self.session.get("uid"))
|
||||
rel_path = self.request.match_info.get("rel_path", "")
|
||||
if rel_path:
|
||||
target = target.joinpath(rel_path)
|
||||
|
||||
if not target.exists():
|
||||
return web.HTTPNotFound(reason="Path not found")
|
||||
|
||||
if target.is_dir():
|
||||
return await self.render_template("drive.html",{"path": rel_path})
|
||||
if target.is_file():
|
||||
return web.FileResponse(target)
|
||||
|
||||
|
||||
class DriveApiView(BaseView):
|
||||
async def get(self):
|
||||
target = await self.services.user.get_home_folder(self.session.get("uid"))
|
||||
rel = self.request.query.get("path", "")
|
||||
offset = int(self.request.query.get("offset", 0))
|
||||
limit = int(self.request.query.get("limit", 20))
|
||||
target = await self.services.user.get_home_folder(self.session.get("uid"))
|
||||
|
||||
if rel:
|
||||
target.joinpath(rel)
|
||||
target = target.joinpath(rel)
|
||||
|
||||
if not target.exists():
|
||||
return web.json_response({"error": "Not found"}, status=404)
|
||||
|
||||
# ---- Directory listing -------------------------------------------
|
||||
if target.is_dir():
|
||||
entries = []
|
||||
# Directories first, then files – both alphabetical (case‑insensitive)
|
||||
for p in sorted(target.iterdir(), key=lambda p: (p.is_file(), p.name.lower())):
|
||||
item_path = (Path(rel) / p.name).as_posix()
|
||||
mime = mimetypes.guess_type(p.name)[0] if p.is_file() else "inode/directory"
|
||||
@ -73,10 +68,6 @@ class DriveView(BaseView):
|
||||
"pagination": {"offset": offset, "limit": limit, "total": total}
|
||||
})
|
||||
|
||||
with open(target, "rb") as f:
|
||||
content = f.read()
|
||||
return web.Response(body=content, content_type=mimetypes.guess_type(target.name)[0])
|
||||
# ---- Single file metadata ----------------------------------------
|
||||
url = self.request.url.with_path(f"/drive/{urllib.parse.quote(rel)}")
|
||||
return web.json_response({
|
||||
"name": target.name,
|
||||
|
Loading…
Reference in New Issue
Block a user