admin-redesign #26
@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{% block title %}Sshecret Admin{% endblock %}</title>
|
||||
|
||||
{% block head %}
|
||||
{% include 'base/partials/stylesheets.html.j2' %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body class="bg-gray-50 text-gray-900 min-h-screen flex flex-col">
|
||||
|
||||
<!-- Optional: Shoelace modals, toasts -->
|
||||
<sl-alert id="global-alert" variant="primary" duration="4000" closable></sl-alert>
|
||||
<sl-dialog id="global-dialog" label="Dialog"></sl-dialog>
|
||||
|
||||
<!-- Layout Container -->
|
||||
<div class="flex flex-1 h-full overflow-hidden">
|
||||
|
||||
<!-- Sidebar -->
|
||||
|
||||
<aside class="hidden md:flex md:w-64 flex-col h-full min-h-screen bg-white border-r border-gray-300" id="sidebar" aria-label="sidebar">
|
||||
{% include "base/partials/sidebar.html.j2" %}
|
||||
</aside>
|
||||
<!-- Main Panel -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
|
||||
<!-- Topbar -->
|
||||
<header class="bg-white border-b px-4 py-3 border-gray-300">
|
||||
{% include "base/partials/navbar.html.j2" %}
|
||||
</header>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<main id="content" class="flex-1 overflow-y-auto" hx-target="this" hx-swap="innerHTML">
|
||||
{% block breadcrumbs %}
|
||||
{% endblock %}
|
||||
<div class="p-4" id="maincontent">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block scripts %}
|
||||
{% include 'base/partials/scripts.html.j2' %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@ -0,0 +1,41 @@
|
||||
{% extends "/base/base.html.j2" %}
|
||||
|
||||
{% block title %}{{ title or "Page" }}{% endblock %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
|
||||
<div class="p-4 bg-white block sm:flex items-center justify-between border-b border-gray-200">
|
||||
<nav class="text-sm text-gray-500" aria-label="Breadcrumb">
|
||||
|
||||
<sl-breadcrumb>
|
||||
<sl-breadcrumb-item>
|
||||
<sl-icon slot="prefix" name="house"></sl-icon>
|
||||
<a href="/">Home</a>
|
||||
</sl-breadcrumb-item>
|
||||
{% if breadcrumbs %}
|
||||
{% for label, url in breadcrumbs %}
|
||||
<sl-breadcrumb-item>
|
||||
{% if url %}
|
||||
<a href="{{url}}">{{label}}</a>
|
||||
{% else %}
|
||||
{{ label }}
|
||||
{% endif %}
|
||||
</sl-breadcrumb-item>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</sl-breadcrumb>
|
||||
</nav>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<!-- Breadcrumbs -->
|
||||
|
||||
<!-- Page Content -->
|
||||
<section>
|
||||
{% block page_content %}
|
||||
<p>This is a generic page.</p>
|
||||
{% endblock %}
|
||||
</section>
|
||||
|
||||
{% endblock %}
|
||||
@ -0,0 +1,77 @@
|
||||
<header class="flex items-center justify-between">
|
||||
|
||||
<!-- Left: Sidebar toggle (for mobile) + Title -->
|
||||
<div class="flex items-center space-x-4">
|
||||
|
||||
<!-- Mobile sidebar toggle -->
|
||||
<button
|
||||
id="sidebar-toggle"
|
||||
aria-expanded="true"
|
||||
aria-controls="mobile-sidebar"
|
||||
class="md:hidden text-gray-600 hover:text-gray-900 focus:outline-none"
|
||||
aria-label="Toggle sidebar"
|
||||
>
|
||||
<sl-icon name="list" class="text-xl"></sl-icon>
|
||||
</button>
|
||||
|
||||
<!-- Page title or logo -->
|
||||
<a href="/" class="flex ml-2 md:mr-24">
|
||||
<img
|
||||
src="{{ url_for('static', path='logo.svg') }}"
|
||||
class="h-11 mr-3"
|
||||
alt="Sshecret Logo"
|
||||
/>
|
||||
<span
|
||||
class="self-center text-xl font-semibold sm:text-2xl whitespace-nowrap dark:text-white"
|
||||
>Sshecret</span
|
||||
>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Right: User menu -->
|
||||
<div class="relative">
|
||||
<button
|
||||
type="button"
|
||||
class="flex text-sm bg-gray-800 rounded-full focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600"
|
||||
id="user-menu-button-2"
|
||||
aria-expanded="false"
|
||||
data-dropdown-toggle="dropdown-2"
|
||||
>
|
||||
<span class="sr-only">Open user menu</span>
|
||||
<sl-avatar label="User avatar"></sl-avatar>
|
||||
</button>
|
||||
<!-- Dropdown placeholder -->
|
||||
<div
|
||||
class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:bg-gray-700 dark:divide-gray-600"
|
||||
id="dropdown-2"
|
||||
>
|
||||
<div class="px-4 py-3" role="none">
|
||||
<p class="text-sm text-gray-900 dark:text-white" role="none">
|
||||
{{ user.display_name }}
|
||||
</p>
|
||||
</div>
|
||||
<ul class="py-1" role="none">
|
||||
{% if user.local %}
|
||||
<li>
|
||||
<a
|
||||
href="/password"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
role="menuitem"
|
||||
>Change Password</a
|
||||
>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a
|
||||
href="/logout"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600 dark:hover:text-white"
|
||||
role="menuitem"
|
||||
>Logout</a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<!-- You can later replace this with a Flowbite dropdown or Shoelace menu -->
|
||||
</div>
|
||||
</header>
|
||||
@ -0,0 +1,26 @@
|
||||
{# <script src="{{ url_for('static', path='js/sidebar.js') }}"></script> #}
|
||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/simple-datatables@9.0.3"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flowbite@3.1.2/dist/flowbite.min.js"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/shoelace-autoloader.js"></script>
|
||||
<script type="text/javascript" src="{{ url_for('static', path="js/prism.js") }}"></script>
|
||||
|
||||
<script>
|
||||
const sidebarToggle = document.getElementById('sidebar-toggle');
|
||||
const sidebarDrawer = document.getElementById('sidebar');
|
||||
|
||||
sidebarToggle?.addEventListener('click', () => {
|
||||
sidebarDrawer.classList.toggle("hidden");
|
||||
});
|
||||
|
||||
document.body.addEventListener("htmx:afterSwap", (e) => {
|
||||
const swappedEl = e.target;
|
||||
|
||||
const initTargets = swappedEl.querySelectorAll(".flowbite-init-target");
|
||||
if (initTargets.length > 0 && typeof window.initFlowbite === "function") {
|
||||
window.initFlowbite();
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
@ -0,0 +1,43 @@
|
||||
<!-- Sidebar Container -->
|
||||
|
||||
<!-- Top: Brand -->
|
||||
<div class="px-4 py-6">
|
||||
|
||||
<a href="/" class="text-xl font-semibold text-gray-800">
|
||||
🐚 Sshecret
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 overflow-y-auto px-4" aria-label="navigation">
|
||||
<ul class="space-y-">
|
||||
|
||||
<li>
|
||||
<a href="/" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100">
|
||||
<sl-icon name="house"></sl-icon>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="/clients/" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100">
|
||||
<sl-icon name="person-fill-lock"> </sl-icon>
|
||||
Clients
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="/admin/secrets" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100">
|
||||
<sl-icon name="database-lock"></sl-icon>
|
||||
Secrets
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<a href="/admin/audit" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100">
|
||||
<sl-icon name="card-list"></sl-icon>
|
||||
Audit Log
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
@ -0,0 +1,37 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('static', path='css/main.css') }}"
|
||||
type="text/css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('static', path='css/prism.css') }}"
|
||||
type="text/css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="{{ url_for('static', path='css/style.css') }}"
|
||||
type="text/css"
|
||||
/>
|
||||
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="(prefers-color-scheme:light)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/light.css"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
media="(prefers-color-scheme:dark)"
|
||||
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/dark.css"
|
||||
onload="document.documentElement.classList.add('sl-theme-dark');"
|
||||
/>
|
||||
@ -0,0 +1,27 @@
|
||||
{% extends 'base/page.html.j2' %}
|
||||
|
||||
|
||||
{% block title %}Clients{% endblock %}
|
||||
|
||||
{% block page_content %}
|
||||
|
||||
<!-- Master-Detail Layout -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-[300px_1fr] gap-4">
|
||||
|
||||
<!-- Master (e.g., tree or list) -->
|
||||
<div id="master-pane">
|
||||
{% block master %}
|
||||
<p>Master list goes here</p>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- Detail (loaded by HTMX or inline) -->
|
||||
<div id="detail-pane" class="bg-white rounded shadow p-4">
|
||||
{% block detail %}
|
||||
<p>Select an item from the list to view details.</p>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@ -1,4 +1,4 @@
|
||||
{% extends "/dashboard/_base.html" %} {% block content %}
|
||||
{% extends "/base/base.html.j2" %} {% block content %}
|
||||
|
||||
<div class="px-4 pt-6">
|
||||
<div class="py-8 px-4 mt-4 mx-auto max-w-screen-xl text-center lg:py-16">
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
{% endif %}
|
||||
|
||||
<div id="main-content" class="relative w-full h-full overflow-y-auto bg-gray-50 lg:ml-64 dark:bg-gray-900 flex flex-col md:flex-row flex-grow">
|
||||
<main class="flex-grow p-4 order-2 md:order-1">
|
||||
<main>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
@ -107,6 +107,37 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
|
||||
},
|
||||
)
|
||||
|
||||
@app.get("/clients/new/")
|
||||
async def get_new_client_tree(
|
||||
request: Request,
|
||||
current_user: Annotated[LocalUserInfo, Depends(dependencies.get_user_info)],
|
||||
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
|
||||
) -> Response:
|
||||
"""Get client tree view."""
|
||||
page = 1
|
||||
per_page = CLIENTS_PER_PAGE
|
||||
offset = 0
|
||||
|
||||
client_filter = ClientFilter(offset=offset, limit=per_page)
|
||||
results = await admin.query_clients(client_filter)
|
||||
paginate = PagingInfo(
|
||||
page=page, limit=per_page, total=results.total_results, offset=offset
|
||||
)
|
||||
|
||||
LOG.info("Results %r", results)
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"clients/redesign.html.j2",
|
||||
{
|
||||
"page_title": "Clients",
|
||||
"offset": offset,
|
||||
"pages": paginate,
|
||||
"clients": results.clients,
|
||||
"user": current_user,
|
||||
"results": results,
|
||||
},
|
||||
)
|
||||
|
||||
@app.get("/clients/page/{page}")
|
||||
async def get_client_page(
|
||||
request: Request,
|
||||
|
||||
@ -2854,6 +2854,11 @@
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
.md\:w-64 {
|
||||
@media (width >= 48rem) {
|
||||
width: calc(var(--spacing) * 64);
|
||||
}
|
||||
}
|
||||
.md\:w-\[calc\(100\%-256px\)\] {
|
||||
@media (width >= 48rem) {
|
||||
width: calc(100% - 256px);
|
||||
@ -2879,6 +2884,11 @@
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
.md\:grid-cols-\[300px_1fr\] {
|
||||
@media (width >= 48rem) {
|
||||
grid-template-columns: 300px 1fr;
|
||||
}
|
||||
}
|
||||
.md\:flex-row {
|
||||
@media (width >= 48rem) {
|
||||
flex-direction: row;
|
||||
|
||||
Reference in New Issue
Block a user