Adding object detail page.

This commit is contained in:
Michael Woods
2025-12-26 14:29:27 -05:00
parent 6dfaaa76d4
commit 7d99eecc61
2 changed files with 143 additions and 1 deletions

View File

@@ -1,10 +1,15 @@
from fastapi import APIRouter, Depends, Request, Form, File, UploadFile
from fastapi.responses import HTMLResponse, RedirectResponse
from uuid import UUID
import base64
from packetserver.http.dependencies import get_current_http_user
from packetserver.http.auth import HttpUser
from packetserver.http.server import templates
from packetserver.http.routers.objects import router as api_router # to call internal endpoints
from packetserver.http.database import DbDependency
from packetserver.http.routers.objects import get_object_metadata as api_get_metadata
router = APIRouter(tags=["objects_html"])
@@ -29,3 +34,54 @@ async def objects_page(
"objects": objects
}
)
@router.get("/objects/{uuid}", response_class=HTMLResponse)
async def object_detail_page(
request: Request,
uuid: UUID,
db: DbDependency,
current_user: HttpUser = Depends(get_current_http_user)
):
# Call the existing metadata API function
obj = api_get_metadata(uuid=uuid, db=db, current_user=current_user)
return templates.TemplateResponse(
"object_detail.html",
{
"request": request,
"current_user": current_user.username,
"obj": obj
}
)
@router.post("/objects/{uuid}")
async def update_object(
db: DbDependency,
uuid: UUID,
request: Request,
name: str = Form(None),
private: str = Form("off"), # checkbox sends "on" if checked
new_text: str = Form(None),
new_file: UploadFile = File(None),
new_base64: str = Form(None),
current_user: HttpUser = Depends(get_current_http_user)
):
payload = {}
if name is not None:
payload["name"] = name
payload["private"] = (private == "on")
if new_text is not None and new_text.strip():
payload["data_text"] = new_text.strip()
elif new_file and new_file.filename:
content = await new_file.read()
payload["data_base64"] = base64.b64encode(content).decode('ascii')
elif new_base64 and new_base64.strip():
payload["data_base64"] = new_base64.strip()
# Call the PATCH API internally (simple requests or direct function call)
from packetserver.http.routers.objects import update_object as api_update
await api_update(uuid=uuid, payload=ObjectUpdate(**payload), db=db, current_user=current_user)
# Redirect back to the detail page (or /objects list)
return RedirectResponse(url=f"/objects/{uuid}", status_code=303)

View File

@@ -0,0 +1,86 @@
{% extends "base.html" %}
{% block title %}{{ obj.name }} - Edit Object{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-8">
<h2>Edit Object: {{ obj.name }}</h2>
<div class="card mb-4">
<div class="card-body">
<form method="post" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" name="name" class="form-control" value="{{ obj.name }}">
</div>
<div class="form-check mb-3">
<input type="checkbox" name="private" class="form-check-input" {% if obj.private %}checked{% endif %}>
<label class="form-check-label">Private (only you can see/download)</label>
</div>
<hr>
<h5>Replace Content</h5>
<div class="mb-3">
<label class="form-label">New Text Content (forces text type)</label>
<textarea name="new_text" class="form-control" rows="8" placeholder="Paste new text here to overwrite as text object"></textarea>
</div>
<div class="mb-3">
<label class="form-label">Or Upload New File (forces binary)</label>
<input type="file" name="new_file" class="form-control">
</div>
<div class="mb-3">
<label class="form-label">Or Paste Base64 (advanced, forces binary)</label>
<textarea name="new_base64" class="form-control" rows="4" placeholder="Paste base64-encoded data"></textarea>
</div>
<button type="submit" class="btn btn-success me-2">Save Changes</button>
<a href="/objects" class="btn btn-secondary">Cancel</a>
</form>
</div>
</div>
<div class="card">
<div class="card-body">
<h5>Danger Zone</h5>
<button type="button" class="btn btn-danger" onclick="deleteObject('{{ obj.uuid }}', '{{ obj.name | e }}')">Delete This Object Permanently</button>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">Current Details</div>
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong>UUID:</strong> {{ obj.uuid }}</li>
<li class="list-group-item"><strong>Size:</strong> {{ obj.size }} bytes</li>
<li class="list-group-item"><strong>Type:</strong> {% if obj.binary %}Binary{% else %}Text{% endif %}</li>
<li class="list-group-item"><strong>Uploaded:</strong> {{ obj.created_at.strftime('%b %d, %Y') }}</li>
<li class="list-group-item"><strong>Modified:</strong> {{ obj.modified_at.strftime('%b %d, %Y') }}</li>
</ul>
<div class="card-body">
<a href="/api/v1/objects/{{ obj.uuid }}/download" class="btn btn-primary w-100 mb-2">Download Current Version</a>
{% if not obj.binary %}
<a href="/api/v1/objects/{{ obj.uuid }}/text" class="btn btn-outline-info w-100" target="_blank">View as Text</a>
{% endif %}
</div>
</div>
</div>
</div>
<script>
async function deleteObject(uuid, name) {
if (!confirm(`Permanently delete "${name}"? This cannot be undone.`)) return;
const response = await fetch(`/api/v1/objects/${uuid}`, { method: 'DELETE' });
if (response.ok) {
window.location.href = '/objects';
} else {
alert('Delete failed');
}
}
</script>
{% endblock %}