UPdate.
This commit is contained in:
		
							parent
							
								
									dd108c2004
								
							
						
					
					
						commit
						f0591d4939
					
				| @ -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