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.avatar import AvatarView | ||||||
| from snek.view.docs import DocsHTMLView, DocsMDView | from snek.view.docs import DocsHTMLView, DocsMDView | ||||||
| from snek.view.drive import DriveView | from snek.view.drive import DriveView | ||||||
|  | from snek.view.drive import DriveApiView | ||||||
| from snek.view.index import IndexView | from snek.view.index import IndexView | ||||||
| from snek.view.login import LoginView | from snek.view.login import LoginView | ||||||
| from snek.view.logout import LogoutView | from snek.view.logout import LogoutView | ||||||
| @ -178,7 +179,8 @@ class Application(BaseApplication): | |||||||
|         self.router.add_view("/threads.html", ThreadsView) |         self.router.add_view("/threads.html", ThreadsView) | ||||||
|         self.router.add_view("/terminal.ws", TerminalSocketView) |         self.router.add_view("/terminal.ws", TerminalSocketView) | ||||||
|         self.router.add_view("/terminal.html", TerminalView) |         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("/drive/{drive}.json", DriveView) | ||||||
|         self.router.add_view("/stats.json", StatsView) |         self.router.add_view("/stats.json", StatsView) | ||||||
|         self.router.add_view("/user/{user}.html", UserView) |         self.router.add_view("/user/{user}.html", UserView) | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ class FileBrowser extends HTMLElement { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   connectedCallback() { |   connectedCallback() { | ||||||
|  |       this.path = this.getAttribute("path") || ""; | ||||||
|     this.renderShell(); |     this.renderShell(); | ||||||
|     this.load(); |     this.load(); | ||||||
|   } |   } | ||||||
| @ -19,11 +20,11 @@ class FileBrowser extends HTMLElement { | |||||||
|       <style> |       <style> | ||||||
|         :host { display:block; font-family: system-ui, sans-serif; box-sizing: border-box; } |         :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; } |         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; } |         button:disabled { background:#999; cursor:not-allowed; } | ||||||
|         .crumb  { font-weight:600; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; } |         .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; } |         .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); } |         .tile:hover { box-shadow:0 2px 8px rgba(0,0,0,.1); } | ||||||
|         img.thumb { width:100%; height:90px; object-fit:cover; border-radius:6px; } |         img.thumb { width:100%; height:90px; object-fit:cover; border-radius:6px; } | ||||||
|         .icon { font-size:48px; line-height:90px; } |         .icon { font-size:48px; line-height:90px; } | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ | |||||||
|     <div class="logo no-select">{% block header_text %}{% endblock %}</div> |     <div class="logo no-select">{% block header_text %}{% endblock %}</div> | ||||||
|     <nav class="no-select" style="overflow:hidden;scroll-behavior:smooth"> |     <nav class="no-select" style="overflow:hidden;scroll-behavior:smooth"> | ||||||
|       <a class="no-select" href="/web.html">🏠</a> |       <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" href="/search-user.html">🔍</a> | ||||||
|       <a class="no-select" style="display:none" id="install-button" href="#">📥</a> |       <a class="no-select" style="display:none" id="install-button" href="#">📥</a> | ||||||
|       <a class="no-select" href="/threads.html">👥</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 header_text %}<h1><i class="fa-solid fa-plus"></i> Create Repository</h1>{% endblock %} | ||||||
| 
 | 
 | ||||||
| {% block main %} | {% block main %} | ||||||
| 
 | {% include 'settings/repositories/form.html' %} | ||||||
| <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> |  | ||||||
|   <div class="container"> |   <div class="container"> | ||||||
|     <form action="/settings/repositories/create.html" method="post"> |     <form action="/settings/repositories/create.html" method="post"> | ||||||
|       <div> |       <div> | ||||||
| @ -52,8 +16,9 @@ button, a.button { | |||||||
|           <i class="fa-solid fa-lock"></i> Private |           <i class="fa-solid fa-lock"></i> Private | ||||||
|         </label> |         </label> | ||||||
|       </div> |       </div> | ||||||
|       <button type="submit"><i class="fa-solid fa-plus"></i> Create</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> Back</button>  |       <button onclick="history.back()" class="cancel"><i class="fa-solid fa-arrow-left"></i>Cancel</button> | ||||||
|  | 
 | ||||||
|     </form> |     </form> | ||||||
|   </div> |   </div> | ||||||
|   {% endblock %} |   {% endblock %} | ||||||
|  | |||||||
| @ -3,33 +3,8 @@ | |||||||
| {% block header_text %}<h1><i class="fa-solid fa-trash-can"></i> Delete Repository</h1>{% endblock %} | {% block header_text %}<h1><i class="fa-solid fa-trash-can"></i> Delete Repository</h1>{% endblock %} | ||||||
| 
 | 
 | ||||||
| {% block main %} | {% block main %} | ||||||
|   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"> |     {% include "settings/repositories/form.html" %}   | ||||||
|   <style> | <div class="container"> | ||||||
|     .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"> |  | ||||||
|       <p>Are you sure you want to <strong>delete</strong> the following repository?</p> |       <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> |       <div class="repo-name"><i class="fa-solid fa-book"></i> {{ repository.name }}</div> | ||||||
|       <form method="post" style="margin-top:1.5rem;"> |       <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,27 +3,7 @@ | |||||||
| {% block header_text %}<h1><i class="fa-solid fa-pen"></i> Update Repository</h1>{% endblock %} | {% block header_text %}<h1><i class="fa-solid fa-pen"></i> Update Repository</h1>{% endblock %} | ||||||
| 
 | 
 | ||||||
| {% block main %} | {% block main %} | ||||||
|   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css"> | {% include "settings/repositories/form.html" %} | ||||||
|   <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"> |     <div class="container"> | ||||||
|     <form method="post"> |     <form method="post"> | ||||||
|       <!-- Assume hidden id for backend use --> |       <!-- Assume hidden id for backend use --> | ||||||
|  | |||||||
| @ -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 aiohttp import web | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| import mimetypes, urllib.parse | 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): | class DriveView(BaseView): | ||||||
|  | 
 | ||||||
|     async def get(self): |     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", "") |         rel = self.request.query.get("path", "") | ||||||
|         offset = int(self.request.query.get("offset", 0)) |         offset = int(self.request.query.get("offset", 0)) | ||||||
|         limit  = int(self.request.query.get("limit", 20)) |         limit  = int(self.request.query.get("limit", 20)) | ||||||
|         target = await self.services.user.get_home_folder(self.session.get("uid")) | 
 | ||||||
|         if rel: |         if rel: | ||||||
|             target.joinpath(rel) |             target = target.joinpath(rel) | ||||||
| 
 | 
 | ||||||
|         if not target.exists(): |         if not target.exists(): | ||||||
|             return web.json_response({"error": "Not found"}, status=404) |             return web.json_response({"error": "Not found"}, status=404) | ||||||
| 
 | 
 | ||||||
|         # ---- Directory listing ------------------------------------------- |  | ||||||
|         if target.is_dir(): |         if target.is_dir(): | ||||||
|             entries = [] |             entries = [] | ||||||
|             # Directories first, then files – both alphabetical (case‑insensitive) |  | ||||||
|             for p in sorted(target.iterdir(), key=lambda p: (p.is_file(), p.name.lower())): |             for p in sorted(target.iterdir(), key=lambda p: (p.is_file(), p.name.lower())): | ||||||
|                 item_path = (Path(rel) / p.name).as_posix() |                 item_path = (Path(rel) / p.name).as_posix() | ||||||
|                 mime = mimetypes.guess_type(p.name)[0] if p.is_file() else "inode/directory" |                 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} |                 "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)}") |         url = self.request.url.with_path(f"/drive/{urllib.parse.quote(rel)}") | ||||||
|         return web.json_response({ |         return web.json_response({ | ||||||
|             "name": target.name, |             "name": target.name, | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user