Finalize secret tree page

This commit is contained in:
2025-06-11 19:10:00 +02:00
parent 0eaa913e35
commit b3debd3ed2
4 changed files with 64 additions and 112 deletions

View File

@ -27,6 +27,11 @@
{% if group.group_name in group_path_nodes %} {% if group.group_name in group_path_nodes %}
expanded="" expanded=""
{% endif %} {% endif %}
{% if selected_group | default(None) %}
{% if group.path == selected_group %}
selected=""
{% endif %}
{% endif %}
{% endif %} {% endif %}
> >
@ -77,7 +82,13 @@
id="secret-group-root-item" id="secret-group-root-item"
data-type="root" data-type="root"
data-name="root" data-name="root"
expanded=""
{% if "/" in group_path_nodes %}
expanded=""
{% endif %}
{% if selected_group == "/"%}
selected=""
{% endif %}
> >
<sl-icon name="folder"> </sl-icon> <sl-icon name="folder"> </sl-icon>
<span class="px-2">Ungrouped</span> <span class="px-2">Ungrouped</span>

View File

@ -1,14 +1,14 @@
<div class="w-full"> <div class="w-full">
<div class="mb-4"> <div class="mb-4">
<h3 class="text-xl font-semibold dark:text-white">Group {{name}}</h3> <h3 class="text-xl font-semibold dark:text-white">Group {{group.group_name}}</h3>
{% if description %} {% if description %}
<span class="text-sm text-gray-500 dark:text-gray-400">{{ description }}</span> <span class="text-sm text-gray-500 dark:text-gray-400">{{ group.description }}</span>
{% endif %} {% endif %}
</div> </div>
<sl-details summary="Create secret"> <sl-details summary="Create secret">
<form <form
hx-post="/secrets/create/group/{{ name }}" hx-post="/secrets/create/group/{{ group.group_name }}"
hx-target="#secretdetails" hx-target="#secretdetails"
hx-swap="OuterHTML" hx-swap="OuterHTML"
> >
@ -48,7 +48,7 @@
placeholder="Description" placeholder="Description"
/> />
</div> </div>
<input type="hidden" name="parent_group" value="{{ name }}" /> <input type="hidden" name="parent_group" value="{{ group.group_name }}" />
<button <button
type="submit" type="submit"
class="text-white w-full justify-center bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800" class="text-white w-full justify-center bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
@ -59,7 +59,7 @@
</sl-details> </sl-details>
<sl-details summary="Edit group"> <sl-details summary="Edit group">
<form <form
hx-put="/secrets/partial/group/{{name}}/description" hx-put="/secrets/partial/group/{{group.group_name}}/description"
hx-target="#secretdetails" hx-target="#secretdetails"
hx-swap="OuterHTML" hx-swap="OuterHTML"
> >
@ -77,7 +77,7 @@
name="description" name="description"
id="description" id="description"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
value="{{ description }}" value="{{ group.description }}"
required="" required=""
/> />
</div> </div>
@ -91,7 +91,7 @@
<button <button
type="button" type="button"
class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-red-700 rounded-lg hover:bg-red-800 focus:ring-4 focus:ring-red-300 dark:focus:ring-red-900" class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-red-700 rounded-lg hover:bg-red-800 focus:ring-4 focus:ring-red-300 dark:focus:ring-red-900"
hx-delete="/secrets/group/{{ name }}" hx-delete="/secrets/group/{{ group.group_name }}"
hx-target="#secretdetails" hx-target="#secretdetails"
hx-swap="OuterHTML" hx-swap="OuterHTML"
hx-confirm="Deleting a group will move all its secrets to the Ungrouped category. Continue?" hx-confirm="Deleting a group will move all its secrets to the Ungrouped category. Continue?"

View File

@ -0,0 +1,7 @@
<div class="w-full" id="secretdetails">
<a
href="{{ destination }}"
class="font-medium text-blue-600 dark:text-blue-500 hover:underline">
Redirecting...
</a>
</div>

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 """Secrets views."""
# pyright: reportUnusedFunction=false # pyright: reportUnusedFunction=false
import os
import logging import logging
import secrets as pysecrets import secrets as pysecrets
from typing import Annotated, Any from typing import Annotated, Any
@ -64,57 +64,17 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
current_user: Annotated[LocalUserInfo, Depends(dependencies.get_user_info)], current_user: Annotated[LocalUserInfo, Depends(dependencies.get_user_info)],
): ):
groups = await admin.get_secret_groups() groups = await admin.get_secret_groups()
LOG.info("Groups: %s", groups.model_dump_json(indent=2))
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"secrets/index.html.j2", "secrets/index.html.j2",
{ {
"groups": groups, "groups": groups,
"user": current_user, "user": current_user,
"selected_group": None,
"group_path_nodes": ["/"],
}, },
) )
# @app.get("/secrets/partial/root_group")
# async def get_root_group(
# request: Request,
# admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
# ):
# """Get root group."""
# clients = await admin.get_clients()
# return templates.TemplateResponse(
# request,
# "secrets/partials/edit_root.html.j2",
# {
# "group_path_nodes": [],
# "clients": clients,
# },
# )
# @app.get("/secrets/partial/secret/{name}")
# async def get_secret_tree_detail_partial(
# request: Request,
# name: str,
# admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
# ):
# """Get partial secret detail."""
# secret = await admin.get_secret(name)
# groups = await admin.get_secret_groups(flat=True)
# events = await admin.get_audit_log_detailed(limit=10, secret_name=name)
# if not secret:
# raise HTTPException(
# status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found"
# )
# return templates.TemplateResponse(
# request,
# "secrets/partials/tree_detail.html.j2",
# {
# "secret": secret,
# "groups": groups,
# "events": events,
# },
# )
@app.get("/secrets/group/") @app.get("/secrets/group/")
async def show_root_group( async def show_root_group(
request: Request, request: Request,
@ -138,6 +98,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
context["user"] = current_user context["user"] = current_user
context["groups"] = groups context["groups"] = groups
context["group_path_nodes"] = ["/"] context["group_path_nodes"] = ["/"]
context["selected_group"] = "/"
return templates.TemplateResponse( return templates.TemplateResponse(
request, template_name, context, headers=headers request, template_name, context, headers=headers
@ -161,8 +122,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
headers: dict[str, str] = {} headers: dict[str, str] = {}
context: dict[str, Any] = { context: dict[str, Any] = {
"group_page": True, "group_page": True,
"name": group.group_name, "group": group,
"description": group.description,
"clients": clients, "clients": clients,
} }
if request.headers.get("HX-Request"): if request.headers.get("HX-Request"):
@ -176,6 +136,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
context["user"] = current_user context["user"] = current_user
context["groups"] = groups context["groups"] = groups
context["group_path_nodes"] = group.path.split("/") context["group_path_nodes"] = group.path.split("/")
context["selected_group"] = group.path
return templates.TemplateResponse( return templates.TemplateResponse(
request, template_name, context, headers=headers request, template_name, context, headers=headers
@ -190,7 +151,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
): ):
"""Get secret detail.""" """Get secret detail."""
secret = await admin.get_secret(name) secret = await admin.get_secret(name)
groups = await admin.get_secret_groups(flat=True) groups = await admin.get_secret_groups()
events = await admin.get_audit_log_detailed(limit=10, secret_name=name) events = await admin.get_audit_log_detailed(limit=10, secret_name=name)
if not secret: if not secret:
@ -222,35 +183,12 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
context["user"] = current_user context["user"] = current_user
context["groups"] = groups context["groups"] = groups
context["group_path_nodes"] = group_path context["group_path_nodes"] = group_path
context["selected_group"] = None
return templates.TemplateResponse( return templates.TemplateResponse(
request, template_name, context, headers=headers request, template_name, context, headers=headers
) )
@app.get("/secrets/partial/group/{name}")
async def get_group_details(
request: Request,
name: str,
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
):
"""Get group details partial."""
group = await admin.get_secret_group(name)
if not group:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Group not found"
)
clients = await admin.get_clients()
return templates.TemplateResponse(
request,
"secrets/partials/group_detail.html.j2",
{
"name": group.group_name,
"description": group.description,
"clients": clients,
},
)
@app.delete("/secrets/group/{name}") @app.delete("/secrets/group/{name}")
async def delete_secret_group( async def delete_secret_group(
request: Request, request: Request,
@ -266,11 +204,15 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
await admin.delete_secret_group(name) await admin.delete_secret_group(name)
headers = {"Hx-Refresh": "true"} new_path = "/secrets/group/"
if group.parent_group:
new_path = os.path.join(new_path, group.parent_group.path)
headers = {"Hx-Redirect": new_path}
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"secrets/partials/default_detail.html.j2", "secrets/partials/redirect.html.j2",
{"destination": new_path},
headers=headers, headers=headers,
) )
@ -288,6 +230,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
description=group.description, description=group.description,
parent_group=group.parent_group, parent_group=group.parent_group,
) )
headers = {"Hx-Refresh": "true"} headers = {"Hx-Refresh": "true"}
return templates.TemplateResponse( return templates.TemplateResponse(
@ -360,8 +303,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
request, request,
"secrets/partials/group_detail.html.j2", "secrets/partials/group_detail.html.j2",
{ {
"name": group.group_name, "group": group,
"description": group.description,
"clients": clients, "clients": clients,
}, },
headers=headers, headers=headers,
@ -449,7 +391,6 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
secret: Annotated[CreateSecret, Form()], secret: Annotated[CreateSecret, Form()],
): ):
"""Create secret in group.""" """Create secret in group."""
LOG.info("secret: %s", secret.model_dump_json(indent=2))
if secret.value: if secret.value:
value = secret.value value = secret.value
else: else:
@ -457,24 +398,14 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
await admin.add_secret(secret.name, value, secret.clients, group=name) await admin.add_secret(secret.name, value, secret.clients, group=name)
headers = {"Hx-Refresh": "true"} new_path = f"/secrets/secret/{secret.name}"
new_secret = await admin.get_secret(secret.name)
groups = await admin.get_secret_groups()
events = await admin.get_audit_log_detailed(limit=10, secret_name=secret.name)
if not new_secret: headers = {"Hx-Redirect": new_path}
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found"
)
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"secrets/partials/tree_detail.html.j2", "secrets/partials/redirect.html.j2",
{ {"destination": new_path},
"secret": new_secret,
"groups": groups,
"events": events,
},
headers=headers, headers=headers,
) )
@ -493,23 +424,15 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
await admin.add_secret(secret.name, value, secret.clients, group=None) await admin.add_secret(secret.name, value, secret.clients, group=None)
headers = {"Hx-Refresh": "true"} new_path = f"/secrets/secret/{secret.name}"
new_secret = await admin.get_secret(secret.name)
groups = await admin.get_secret_groups()
events = await admin.get_audit_log_detailed(limit=10, secret_name=secret.name)
if not new_secret: headers = {"Hx-Redirect": new_path}
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Secret not found"
)
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"secrets/partials/tree_detail.html.j2", "secrets/partials/redirect.html.j2",
{ {
"secret": new_secret, "destination": new_path,
"groups": groups,
"events": events,
}, },
headers=headers, headers=headers,
) )
@ -598,12 +521,23 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)], admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
): ):
"""Delete a secret.""" """Delete a secret."""
secret = await admin.get_secret(name)
if not secret:
raise HTTPException(status_code=404, detail="Secret not found")
new_path = "/secrets/group/"
if secret.group:
secret_group = await admin.get_secret_group(secret.group)
if secret_group:
new_path = os.path.join("/secrets/group", secret_group.path)
await admin.delete_secret(name) await admin.delete_secret(name)
headers = {"Hx-Refresh": "true"} headers = {"Hx-Redirect": new_path}
# headers["HX-Push-Url"] = request.url.path
return templates.TemplateResponse( return templates.TemplateResponse(
request, request,
"secrets/partials/default_detail.html.j2", "secrets/partials/redirect.html.j2",
{"destination": new_path},
headers=headers, headers=headers,
) )