Compare commits

...

8 Commits

Author SHA1 Message Date
2b50c686d0 Add heading back 2025-06-19 07:23:26 +02:00
57e69390b2 Fix dark-mode 2025-06-19 07:21:36 +02:00
23d354bc12 Clean up style elements 2025-06-19 06:43:36 +02:00
cad9849019 Update audit and change-password page 2025-06-19 06:23:19 +02:00
b4c395f0da Update secrets page to new layout 2025-06-19 06:12:36 +02:00
d55c699549 Set breadcrumb dynamically 2025-06-18 19:15:12 +02:00
05775a2e1e Complete inital re-design of client page 2025-06-18 08:54:45 +02:00
9b0588679f fix tabs and tables 2025-06-18 08:34:45 +02:00
29 changed files with 484 additions and 491 deletions

View File

@ -1,60 +1,6 @@
{% extends "/dashboard/_base.html" %} {% block content %}
<div
class="p-4 bg-white block sm:flex items-center justify-between border-b border-gray-200 lg:mt-1.5 dark:bg-gray-800 dark:border-gray-700"
>
<div class="w-full mb-1">
<div class="mb-4">
<nav class="flex mb-5" aria-label="Breadcrumb">
<ol
class="inline-flex items-center space-x-1 text-sm font-medium md:space-x-2"
>
<li class="inline-flex items-center">
<a
href="/"
class="inline-flex items-center text-gray-700 hover:text-primary-600 dark:text-gray-300 dark:hover:text-white"
>
<svg
class="w-5 h-5 mr-2.5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"
></path>
</svg>
Home
</a>
</li>
<li>
<div class="flex items-center">
<svg
class="w-6 h-6 text-gray-800 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 8h6m-6 4h6m-6 4h6M6 3v18l2-2 2 2 2-2 2 2 2-2 2 2V3l-2 2-2-2-2 2-2-2-2 2-2-2Z"
/>
<span class="ml-1 text-gray-400 md:ml-2 dark:text-gray-500" aria-current="page">Audit Log</span>
</svg>
</div>
</li>
</ol>
</nav>
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Audit Log</h1>
</div>
</div>
</div>
{% extends "/base/page.html.j2" %}
{% block title %}Audit{% endblock %}
{% block page_content %}
<div id="auditContent">
{% include 'audit/inner.html.j2' %}
</div>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -9,25 +9,21 @@
{% 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>
<body class="bg-gray-50 text-gray-900 dark:bg-gray-900 min-h-screen flex flex-col">
<!-- 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">
<aside class="hidden md:flex md:w-64 flex-col h-full min-h-screen bg-white border-r border-gray-300 dark:bg-gray-800 dark:border-gray-700" 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">
<header class="bg-white border-b px-4 py-3 border-gray-300 dark:bg-gray-800 dark:border-gray-700">
{% include "base/partials/navbar.html.j2" %}
</header>
@ -35,7 +31,7 @@
<main id="content" class="flex-1 overflow-y-auto" hx-target="this" hx-swap="innerHTML">
{% block breadcrumbs %}
{% endblock %}
<div class="p-4" id="maincontent">
<div class="" id="maincontent">
{% block content %}{% endblock %}
</div>
</main>
@ -43,8 +39,12 @@
</div>
</div>
{% block scripts %}
{% include 'base/partials/scripts.html.j2' %}
{% endblock %}
{% block local_scripts %}
{% endblock %}
</body>
</html>

View File

@ -0,0 +1,26 @@
{% extends 'base/page.html.j2' %}
{% block page_content %}
<!-- Master-Detail Split View -->
<div class="flex h-[calc(100vh-8rem)] overflow-hidden">
<!-- Master Pane -->
<aside id="master-pane"
class="md:w-80 w-full shrink-0 border-r overflow-y-auto bg-white md:block border-gray-200 p-4 dark:bg-gray-800 dark:border-gray-700">
{% block master %}
<p class="p-4 text-gray-500">Master view (e.g. list/tree)</p>
{% endblock %}
</aside>
<!-- Detail Pane -->
<section id="detail-pane"
class="flex-1 flex overflow-y-auto bg-white p-4 hidden md:block dark:bg-gray-800">
{% block detail %}
<p class="p-4 text-gray-500 dark:text-gray-200">Select an item to view details</p>
{% endblock %}
</section>
</div>
{% endblock %}

View File

@ -1,20 +1,19 @@
{% 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">
<div class="p-4 bg-white block sm:flex items-center justify-between border-b border-gray-200 dark:bg-gray-800 dark:border-gray-700">
<nav class="text-sm text-gray-500" aria-label="Breadcrumb">
<sl-breadcrumb>
<sl-breadcrumb id="breadcrumbs">
<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>
<sl-breadcrumb-item class="page-breadcrumb">
{% if url %}
<a href="{{url}}">{{label}}</a>
{% else %}
@ -32,7 +31,7 @@
<!-- Breadcrumbs -->
<!-- Page Content -->
<section>
<section class="bg-white dark:bg-gray-800">
{% block page_content %}
<p>This is a generic page.</p>
{% endblock %}

View File

@ -15,18 +15,9 @@
</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>
{% if page_title %}
<h1 class="text-xl flex ml-2 md:mr-24 font-semibold text-gray-900 sm:text-2xl dark:text-white">{{page_title}}</h1>
{% endif %}
</div>
<!-- Right: User menu -->

View File

@ -3,8 +3,9 @@
<!-- Top: Brand -->
<div class="px-4 py-6">
<a href="/" class="text-xl font-semibold text-gray-800">
🐚 Sshecret
<a href="/" class="text-xl font-semibold text-gray-800 dark:text-gray-100">
<sl-icon src="{{ url_for('static', path='logo.svg') }}"></sl-icon>
Sshecret
</a>
</div>
@ -12,28 +13,28 @@
<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">
<a href="/" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">
<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">
<a href="/clients/" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">
<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">
<a href="/secrets" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">
<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">
<a href="/audit" class="flex items-center px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-700">
<sl-icon name="card-list"></sl-icon>
Audit Log
</a>

View File

@ -35,3 +35,16 @@
href="https://cdn.jsdelivr.net/npm/@shoelace-style/shoelace@2.20.1/cdn/themes/dark.css"
onload="document.documentElement.classList.add('sl-theme-dark');"
/>
<script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
if (
localStorage.getItem("color-theme") === "dark" ||
(!("color-theme" in localStorage) &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
</script>

View File

@ -1,5 +1,9 @@
{% extends "/dashboard/_base.html" %} {% block content %}
<div class="min-h-screen bg-gray-100 flex items-center justify-center p-4">
{% extends "/base/page.html.j2" %}
{% block title %}Change Password{% endblock %}
{% block page_content %}
<div class="h-[calc(100vh-8rem)] bg-gray-100 flex items-center justify-center p-4">
<div class="w-full max-w-xl p-6 space-y-8 bg-white rounded-lg shadow sm:p-8 dark:bg-gray-800">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white">
Change Password

View File

@ -1,5 +1,8 @@
{% extends "/dashboard/_base.html" %} {% block content %}
<div class="min-h-screen bg-gray-100 flex items-center justify-center p-4">
{% extends "/base/page.html.j2" %}
{% block page_content %}
<div class="h-[calc(100vh-8rem)] bg-gray-100 flex items-center justify-center p-4">
<div class="text-center xl:max-w-4xl">
<h1 class="mb-3 text-2xl font-bold leading-tight text-gray-900 sm:text-4xl lg:text-5xl dark:text-white">Password Changed</h1>
<p class="mb-5 text-base font-normal text-gray-500 md:text-lg dark:text-gray-400">Your password was changed sucessfully. Next time you log in, use your new password.</p>
@ -9,4 +12,4 @@
</a>
</div>
</div>
{% endblock content %}
{% endblock %}

View File

@ -1,46 +1,24 @@
{% extends "/dashboard/_base.html" %} {% block content %}
<div class="p-4 bg-white block sm:flex items-center justify-between border-b border-gray-200 lg:mt-1.5 dark:bg-gray-800 dark:border-gray-700">
<div class="w-full mb-1">
<div class="mb-4">
<nav class="flex mb-5" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 text-sm font-medium md:space-x-2">
<li class="inline-flex items-center">
<a href="/" class="inline-flex items-center text-gray-700 hover:text-primary-600 dark:text-gray-300 dark:hover:text-white">
<svg class="w-5 h-5 mr-2.5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path></svg>
Home
</a>
</li>
<li>
<div class="flex items-center">
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
<span class="ml-1 text-gray-400 md:ml-2 dark:text-gray-500" aria-current="page">Clients</span>
</div>
</li>
<li>
<div class="flex items-center">
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
<span class="ml-1 text-gray-400 md:ml-2 dark:text-gray-500" aria-current="page">View</span>
</div>
</li>
</ol>
</nav>
</div>
<div class="grid w-full grid-cols-1 gap-4 mt-4 xl:grid-cols-3">
<div class="p-4 bg-white border border-gray-200 rounded-lg shadow-sm sm:flex dark:border-gray-700 sm:p-6 dark:bg-gray-800 h-full" id="client-tree">
{% include '/clients/partials/tree.html.j2' %}
</div>
<div class="2xl:col-span-2 xl:col-span-2 p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
<div class="w-full" id="clientdetails">
{% include '/clients/partials/client_details.html.j2' %}
</div>
</div>
</div>
</div>
</div>
{% include '/clients/partials/drawer_create.html.j2' %}
{% extends 'base/master-detail-email.html.j2' %}
{% block title %}Client {{ client.name }}{% endblock %}
{% block master %}
{% include '/clients/partials/tree.html.j2' %}
{% endblock %}
{% block detail %}
<div id="clientdetails" class="w-full">
{% include '/clients/partials/client_details.html.j2' %}
</div>
{% endblock %}
{% include '/clients/partials/drawer_create.html.j2' %}
{% block local_scripts %}
<script>
{% include '/clients/partials/tree_event.js' %}
</script>
{% endblock local_scripts %}

View File

@ -1,41 +1,21 @@
{% extends "/dashboard/_base.html" %} {% block content %}
<div class="p-4 bg-white block sm:flex items-center justify-between border-b border-gray-200 lg:mt-1.5 dark:bg-gray-800 dark:border-gray-700">
<div class="w-full mb-1">
<div class="mb-4">
<nav class="flex mb-5" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 text-sm font-medium md:space-x-2">
<li class="inline-flex items-center">
<a href="/" class="inline-flex items-center text-gray-700 hover:text-primary-600 dark:text-gray-300 dark:hover:text-white">
<svg class="w-5 h-5 mr-2.5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path></svg>
Home
</a>
</li>
<li>
<div class="flex items-center">
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
<span class="ml-1 text-gray-400 md:ml-2 dark:text-gray-500" aria-current="page">Clients</span>
</div>
</li>
</ol>
</nav>
</div>
<div class="grid w-full grid-cols-1 gap-4 mt-4 xl:grid-cols-3">
<div class="p-4 bg-white border border-gray-200 rounded-lg shadow-sm sm:flex dark:border-gray-700 sm:p-6 dark:bg-gray-800 h-full" id="client-tree">
{% include '/clients/partials/tree.html.j2' %}
</div>
<div class="2xl:col-span-2 xl:col-span-2 p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
<div class="w-full" id="clientdetails">
<h3 class="mb-4 text-sm italic text-gray-400 dark:text-white">Click an item to view details</h3>
</div>
</div>
</div>
</div>
</div>
{% include '/clients/partials/drawer_create.html.j2' %}
{% extends 'base/master-detail-email.html.j2' %}
{% block title %}Clients{% endblock %}
{% block master %}
{% include '/clients/partials/tree.html.j2' %}
{% endblock %}
{% block detail %}
<div id="clientdetails" class="w-full bg-white dark:bg-gray-800">
<h3 class="mb-4 text-sm italic text-gray-400 dark:text-white">Click an item to view details</h3>
</div>
{% include '/clients/partials/drawer_create.html.j2' %}
{% endblock %}
{% block local_scripts %}
<script>
{% include '/clients/partials/tree_event.js' %}
</script>
{% endblock local_scripts %}

View File

@ -38,7 +38,7 @@
</ul>
</div>
</div>
<sl-tab-group placement="end">
<sl-tab-group >
<sl-tab slot="nav" panel="client_data">Client Data</sl-tab>
<sl-tab slot="nav" panel="events">Events</sl-tab>
@ -47,37 +47,37 @@
<div id="client_details">
<div class="w-full p-2">
<div class="px-4 sm:px-0">
<h3 class="text-base/7 font-semibold text-gray-900">{{client.name}}</h3>
<h3 class="text-base/7 font-semibold text-gray-900 dark:text-gray-50">{{client.name}}</h3>
{% if client.description %}
<p class="mt-1 max-w-2xl text-sm/6 text-gray-500">{{ client.description }}</p>
<p class="mt-1 max-w-2xl text-sm/6 text-gray-500 dark:text-gray-100">{{ client.description }}</p>
{% endif %}
</div>
<div class="mt-6 border-t border-gray-100">
<dl class="divide-y divide-gray-100">
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm/6 font-medium text-gray-900">Client ID</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0">{{client.id}}</dd>
<dt class="text-sm/6 font-medium text-gray-900 dark:text-gray-200">Client ID</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0 dark:text-gray-300">{{client.id}}</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm/6 font-medium text-gray-900">Client Description</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0">{{client.description}}</dd>
<dt class="text-sm/6 font-medium text-gray-900 dark:text-gray-200">Client Description</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0 dark:text-gray-300">{{client.description}}</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm/6 font-medium text-gray-900">Client Version</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0">{{client.version}}</dd>
<dt class="text-sm/6 font-medium text-gray-900 dark:text-gray-200">Client Version</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0 dark:text-gray-300">{{client.version}}</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm/6 font-medium text-gray-900">Public Key</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0 truncate">{{client.public_key}}</dd>
<dt class="text-sm/6 font-medium text-gray-900 dark:text-gray-200">Public Key</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0 dark:text-gray-300 truncate">{{client.public_key}}</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm/6 font-medium text-gray-900">Assigned Secrets</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0">{{client.secrets|length}}</dd>
<dt class="text-sm/6 font-medium text-gray-900 dark:text-gray-200">Assigned Secrets</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0 dark:text-gray-300">{{client.secrets|length}}</dd>
</div>
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm/6 font-medium text-gray-900">Allowed sources</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0">{{client.policies|join(', ')}}</dd>
<dt class="text-sm/6 font-medium text-gray-900 dark:text-gray-200">Allowed sources</dt>
<dd class="mt-1 text-sm/6 text-gray-700 sm:col-span-2 sm:mt-0 dark:text-gray-300">{{client.policies|join(', ')}}</dd>
</div>
</dl>
</div>
@ -86,7 +86,7 @@
</sl-tab-panel>
<sl-tab-panel name="events">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600" id="last-audit-events">
<table class="min-w-full lg:table-fixed divide-y divide-gray-200 dark:divide-gray-600" id="last-audit-events">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th scope="col" class="p-4 text-xs font-medium tracking-wider text-left text-gray-500 uppercase dark:text-white">Timestamp</th>

View File

@ -1,67 +1,64 @@
<div class="flex flex-1 flex-col justify-between h-full flowbite-init-target">
<div class="grid grid-cols-2 place-content-between mb-6">
<div>
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Clients</h1>
<div class="flex">
<div
class="htmx-indicator mt-2"
id="client-spinner">
<div role="status">
<svg aria-hidden="true" class="inline w-6 h-6 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
</svg>
<span class="sr-only">Loading...</span>
{# This is the master block #}
<div class="flowbite-init-target">
<div class="tree-header grid grid-cols-2 place-content-between mb-6">
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Client List</h1>
<div class="flex">
<div
class="htmx-indicator mt-2"
id="client-spinner">
<div role="status">
<svg aria-hidden="true" class="inline w-6 h-6 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
</svg>
<span class="sr-only">Loading...</span>
</div>
</div>
<div
class="flex w-full justify-end"
>
<sl-icon-button
name="plus-square"
label="Add Client"
data-drawer-target="drawer-create-client-default"
data-drawer-show="drawer-create-client-default"
aria-controls="drawer-create-client-default"
data-drawer-placement="right"
></sl-icon-button>
</div>
</div>
<div class="col-span-full">
<div class="relative">
<div class="border-b border-gray-200 py-2 mb-6">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Search</label>
<div class="relative">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
</svg>
</div>
<input
type="search"
id="client-search"
name="query"
class="block w-full p-2.5 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-gray-900 focus:border-gray-900 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-gray-900 dark:focus:border-gray-900"
placeholder="Search..."
required
hx-post="/clients/query"
hx-trigger="input changed delay:500ms, keyup[key=='Enter']"
hx-target="#client-tree-items"
hx-indicator="#client-spinner"
/>
</div>
</div>
</div>
</div>
<div
class="flex justify-end px-4"
>
<sl-icon-button
name="plus-square"
label="Add Client"
data-drawer-target="drawer-create-client-default"
data-drawer-show="drawer-create-client-default"
aria-controls="drawer-create-client-default"
data-drawer-placement="right"
></sl-icon-button>
</div>
</div>
<div class="flex-1 overflow-auto">
<div class="relative w-full ">
<div class="border-b border-gray-200 py-2 mb-6">
<label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Search</label>
<div class="relative">
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
</svg>
</div>
<input
type="search"
id="client-search"
name="query"
class="block w-full p-2.5 ps-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-gray-900 focus:border-gray-900 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-gray-900 dark:focus:border-gray-900"
placeholder="Search..."
required
hx-post="/clients/query"
hx-trigger="input changed delay:500ms, keyup[key=='Enter']"
hx-target="#client-tree-items"
hx-indicator="#client-spinner"
/>
</div>
</div>
</div>
<div id="client-tree-items">
{% include '/clients/partials/tree_items.html.j2' %}
</div>
<div id="client-tree-items">
{% include '/clients/partials/tree_items.html.j2' %}
</div>
</div>
<script>
{% include '/clients/partials/tree_event.js' %}
</script>

View File

@ -1,3 +1,16 @@
function setBreadcrumb(name) {
// Set the current client name as the final breadcrumb
const breadcrumbs = document.getElementById("breadcrumbs");
const existingNode = document.getElementById("bc-dynamic-client");
if (existingNode) {
breadcrumbs.removeChild(existingNode);
}
const newCrumb = document.createElement("sl-breadcrumb-item");
newCrumb.setAttribute("id", "bc-dynamic-client");
const bcTitle = document.createTextNode(name);
newCrumb.appendChild(bcTitle);
breadcrumbs.appendChild(newCrumb);
}
function addTreeListener() {
const tree = document.querySelector("sl-tree");
@ -8,6 +21,8 @@ function addTreeListener() {
if (!selectedEl) return;
const masterPane = document.getElementById("master-pane");
const detailPane = document.getElementById("detail-pane");
const type = selectedEl.dataset.nodeType;
const clientId = selectedEl.dataset.clientId;
const name = selectedEl.dataset.clientName;
@ -17,11 +32,17 @@ function addTreeListener() {
let url = `/clients/client/${encodeURIComponent(clientId)}`;
if (url) {
htmx.ajax("GET", url, {
target: "#clientdetails",
//swap: 'OuterHTML',
indicator: "#client-spinner",
});
htmx
.ajax("GET", url, {
target: "#clientdetails",
//swap: 'OuterHTML',
indicator: "#client-spinner",
})
.then(() => {
masterPane.classList.add("hidden");
detailPane.classList.remove("hidden");
setBreadcrumb(name);
});
}
});
}

View File

@ -38,5 +38,4 @@
{% include 'clients/partials/pagination.html.j2' %}
</div>
{% endif %}
</div>

View File

@ -1,4 +1,4 @@
{% extends "/shared/_base.html" %} {% block content %} {% if login_error %}
{% extends "/base/bare.html.j2" %} {% block content %} {% if login_error %}
<div class="flex bg-gray-100">
<div
@ -88,7 +88,5 @@
</a>
</div>
</div>
{% endif %}
{% endblock %}
{% endif %} {% endblock %}
</div>

View File

@ -47,85 +47,67 @@
{% endmacro %}
{% extends "/dashboard/_base.html" %} {% block content %}
{% extends 'base/master-detail-email.html.j2' %}
<div class="p-4 bg-white block sm:flex items-center justify-between border-b border-gray-200 lg:mt-1.5 dark:bg-gray-800 dark:border-gray-700">
<div class="w-full mb-1">
<div class="mb-4">
<nav class="flex mb-5" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1 text-sm font-medium md:space-x-2">
<li class="inline-flex items-center">
<a href="/" class="inline-flex items-center text-gray-700 hover:text-primary-600 dark:text-gray-300 dark:hover:text-white">
<svg class="w-5 h-5 mr-2.5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M10.707 2.293a1 1 0 00-1.414 0l-7 7a1 1 0 001.414 1.414L4 10.414V17a1 1 0 001 1h2a1 1 0 001-1v-2a1 1 0 011-1h2a1 1 0 011 1v2a1 1 0 001 1h2a1 1 0 001-1v-6.586l.293.293a1 1 0 001.414-1.414l-7-7z"></path></svg>
Home
</a>
</li>
<li>
<div class="flex items-center">
<svg class="w-6 h-6 text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>
<span class="ml-1 text-gray-400 md:ml-2 dark:text-gray-500" aria-current="page">Secrets</span>
</div>
</li>
</ol>
</nav>
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Secrets</h1>
</div>
<div class="grid w-full grid-cols-1 gap-4 mt-4 xl:grid-cols-3">
<div class="p-4 bg-white border border-gray-200 rounded-lg shadow-sm sm:flex dark:border-gray-700 sm:p-6 dark:bg-gray-800" id="secret-tree">
{% block title %}Secrets{% endblock %}
<div class="flex flex-1 flex-col">
<div class="h-full w-full">
<sl-tree class="tree-with-icons">
<sl-tree-item
id="secret-group-root-item"
data-type="root"
data-name="root"
{% block master %}
{% if "/" in group_path_nodes %}
expanded=""
{% endif %}
{% if selected_group == "/"%}
selected=""
{% endif %}
>
<sl-icon name="folder"> </sl-icon>
<span class="px-2">Ungrouped</span>
{% for entry in groups.ungrouped %}
{{ display_entry(entry) }}
{% endfor %}
</sl-tree-item>
{% for child in groups.groups %}
{{ display_group(child) }}
{% endfor %}
</sl-tree>
</div>
</div>
<div class="flowbite-init-target">
<div id="secret-tree">
<sl-tree class="tree-with-icons">
<sl-tree-item
id="secret-group-root-item"
data-type="root"
data-name="root"
</div>
<div class="2xl:col-span-2 xl:col-span-2 p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
{% if group_page | default(false) %}
<div class="w-full" id="secretdetails">
{% include '/secrets/partials/group_detail.html.j2' %}
</div>
{% elif root_group_page | default(false) %}
<div class="w-full" id="secretdetails">
{% include '/secrets/partials/edit_root.html.j2' %}
</div>
{% elif secret_page | default(false) %}
<div class="w-full" id="secretdetails">
{% include '/secrets/partials/tree_detail.html.j2' %}
</div>
{% else %}
{% include '/secrets/partials/default_detail.html.j2' %}
{% if "/" in group_path_nodes %}
expanded=""
{% endif %}
</div>
</div>
{% if selected_group == "/"%}
selected=""
{% endif %}
>
<sl-icon name="folder"> </sl-icon>
<span class="px-2">Ungrouped</span>
{% for entry in groups.ungrouped %}
{{ display_entry(entry) }}
{% endfor %}
</sl-tree-item>
{% for child in groups.groups %}
{{ display_group(child) }}
{% endfor %}
</sl-tree>
</div>
</div>
{% endblock %}
{% block detail %}
{% if group_page | default(false) %}
<div class="w-full" id="secretdetails">
{% include '/secrets/partials/group_detail.html.j2' %}
</div>
{% elif root_group_page | default(false) %}
<div class="w-full" id="secretdetails">
{% include '/secrets/partials/edit_root.html.j2' %}
</div>
{% elif secret_page | default(false) %}
<div class="w-full" id="secretdetails">
{% include '/secrets/partials/tree_detail.html.j2' %}
</div>
{% else %}
{% include '/secrets/partials/default_detail.html.j2' %}
{% endif %}
{% endblock %}
{% block local_scripts %}
<script>
{% include '/secrets/partials/tree_event.js' %}
</script>
{% endblock %}

View File

@ -1,4 +1,4 @@
<div class="w-full my-2">
<div class="w-full my-2 dark:text-white">
<ul class="w-48 text-sm font-medium text-gray-900 bg-white dark:bg-gray-700 dark:text-white" id="secretclientlist">
{% include '/secrets/partials/client_list_inner.html.j2' %}
</ul>

View File

@ -1,4 +1,4 @@
<div class="w-full">
<div class="w-full dark:text-white">
<sl-details summary="Create secret">
<form
hx-post="/secrets/create/root"

View File

@ -1,4 +1,4 @@
<div class="w-full">
<div class="w-full dark:text-white">
<div class="mb-4">
<h3 class="text-xl font-semibold dark:text-white">Group {{group.group_name}}</h3>
{% if description %}

View File

@ -1,33 +1,33 @@
<div class="w-full flowbite-init-target" id="secretdetails">
<div class="w-full flowbite-init-target dark:text-white" id="secretdetails">
<!-- menu -->
<!-- menu -->
<div class="flex justify-end px-4">
<button id="secret-menu-button" data-dropdown-toggle="secret-edit-menu" class="inline-block text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-4 focus:outline-none focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-1.5" type="button">
<span class="sr-only">Open dropdown</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 3">
<path d="M2 0a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm6.041 0a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM14 0a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Z"/>
</svg>
</button>
<!-- Dropdown menu -->
<div id="secret-edit-menu" class="z-10 hidden text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow-sm w-44 dark:bg-gray-700">
<ul class="py-2" aria-labelledby="secret-menu-button">
<li>
<a
href="#"
class="block px-4 py-2 text-sm text-red-600 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
hx-delete="/secrets/{{secret.name}}"
hx-target="#secretdetails"
hx-swap="OuterHTML"
hx-indicator=".secret-spinner"
hx-confirm="Really delete this secret?"
>
Delete
</a>
</li>
</ul>
<div class="flex justify-end px-4">
<button id="secret-menu-button" data-dropdown-toggle="secret-edit-menu" class="inline-block text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-4 focus:outline-none focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-1.5" type="button">
<span class="sr-only">Open dropdown</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 3">
<path d="M2 0a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm6.041 0a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM14 0a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Z"/>
</svg>
</button>
<!-- Dropdown menu -->
<div id="secret-edit-menu" class="z-10 hidden text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow-sm w-44 dark:bg-gray-700">
<ul class="py-2" aria-labelledby="secret-menu-button">
<li>
<a
href="#"
class="block px-4 py-2 text-sm text-red-600 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white"
hx-delete="/secrets/{{secret.name}}"
hx-target="#secretdetails"
hx-swap="OuterHTML"
hx-indicator=".secret-spinner"
hx-confirm="Really delete this secret?"
>
Delete
</a>
</li>
</ul>
</div>
</div>
</div>
<h3 class="mb-4 text-xl font-semibold dark:text-white">{{secret.name}}</h3>
@ -53,65 +53,65 @@
</div>
</sl-details>
{% if secret.secret %}
<sl-details summary="Read/Update Secret">
<div id="secretvalue">
<div class="mb-6">
<label for="secret-value" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Value</label>
</div>
<div class="flex w-full">
<div class="relative w-full">
<input type="text" id="disabled-input" aria-label="disabled input" class="mb-6 bg-gray-100 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 cursor-not-allowed dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="••••••••" disabled>
</div>
<div class="px-2.5 mb-2">
<button
type="button"
class="text-gray-900 hover:text-blue-700 border border-gray-200 hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-500 dark:focus:ring-blue-800"
hx-get="/secrets/partial/{{ secret.name }}/viewsecret"
hx-target="#secretvalue"
hx-trigger="click"
hx-indicator="#secretupdatespinner"
>
View
</button>
</div>
</div>
</div>
<div class="htmx-indicator" id="secretupdatespinner">
<div role="status">
<svg aria-hidden="true" class="inline w-4 h-4 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
</svg>
<span class="sr-only">Loading...</span>
</div>
</div>
</sl-details>
{% if groups.groups %}
<sl-details summary="Group">
<form
hx-put="/secrets/set-group/{{ secret.name }}"
hx-target="#secretdetails"
hx-swap="OuterHTML"
hx-indicator=".secret-spinner"
>
<sl-details summary="Read/Update Secret">
<div id="secretvalue">
<div class="mb-6">
<label for="secret-value" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Value</label>
</div>
<div class="flex w-full">
<div class="relative w-full">
<select id="group_name" name="group_name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<option value="__ROOT">Ungrouped</option>
{% for group in groups.groups %}
<option value="{{ group.group_name }}" {% if group.name == secret.group -%}selected{% endif %}>{{ group.path }}</option>
{% endfor %}
</select>
<input type="text" id="disabled-input" aria-label="disabled input" class="mb-6 bg-gray-100 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 cursor-not-allowed dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-gray-400 dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="••••••••" disabled>
</div>
<div class="px-2.5 mb-2">
<button type="Submit" class="text-gray-900 hover:text-blue-700 border border-gray-200 hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-500 dark:focus:ring-blue-800">Update</button>
<button
type="button"
class="text-gray-900 hover:text-blue-700 border border-gray-200 hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-500 dark:focus:ring-blue-800"
hx-get="/secrets/partial/{{ secret.name }}/viewsecret"
hx-target="#secretvalue"
hx-trigger="click"
hx-indicator="#secretupdatespinner"
>
View
</button>
</div>
</div>
</form>
</div>
<div class="htmx-indicator" id="secretupdatespinner">
<div role="status">
<svg aria-hidden="true" class="inline w-4 h-4 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
</svg>
<span class="sr-only">Loading...</span>
</div>
</div>
</sl-details>
{% if groups.groups %}
<sl-details summary="Group">
<form
hx-put="/secrets/set-group/{{ secret.name }}"
hx-target="#secretdetails"
hx-swap="OuterHTML"
hx-indicator=".secret-spinner"
>
<div class="flex w-full">
<div class="relative w-full">
<select id="group_name" name="group_name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<option value="__ROOT">Ungrouped</option>
{% for group in groups.groups %}
<option value="{{ group.group_name }}" {% if group.name == secret.group -%}selected{% endif %}>{{ group.path }}</option>
{% endfor %}
</select>
</div>
<div class="px-2.5 mb-2">
<button type="Submit" class="text-gray-900 hover:text-blue-700 border border-gray-200 hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2 dark:border-blue-500 dark:text-blue-500 dark:hover:text-white dark:hover:bg-blue-500 dark:focus:ring-blue-800">Update</button>
</div>
</div>
</form>
</sl-details>
{% endif %}
{% endif %}
{% endif %}
<sl-details summary="Events">
<sl-details summary="Events" class="dark:text-white">
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-600" id="last-audit-events">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>

View File

@ -1,4 +1,61 @@
document.addEventListener("DOMContentLoaded", () => {
function createCrumb(name, url = null) {
// Create a breadcrumb
const crumb = document.createElement("sl-breadcrumb-item");
crumb.classList.add("page-breadcrumb");
if (url) {
var crumbChild = document.createElement("a");
crumbChild.setAttribute("href", url);
const crumbChildText = document.createTextNode(name);
crumbChild.appendChild(crumbChildText);
} else {
var crumbChild = document.createTextNode(name);
}
crumb.appendChild(crumbChild);
return crumb;
}
function setGroupBreadcrumbs(name, path, secret = null) {
// Set breadcrumbs for a whole group.
const breadcrumbs = document.getElementById("breadcrumbs");
// First, remove all existing page breadcrumbs
console.log(`setGroupBreadcrumbs: ${name} ${path}`);
let pageCrumbs = document.getElementsByClassName("page-breadcrumb");
for (let i = 0; i < pageCrumbs.length; i++) {
breadcrumbs.removeChild(pageCrumbs[i]);
}
// Re-create the breadcrumbs
const newcrumbs = [
["Secrets", "/secrets/"],
["Groups", "/secrets/groups/"],
];
if (path) {
const pathnodes = path.split("/");
for (let i = 0; i < pathnodes.length; i++) {
let pathnode = pathnodes[i];
let nextnode = i + 1;
let groupPathNodes = pathnodes.slice(0, nextnode);
let groupPath = groupPathNodes.join("/");
newcrumbs.push([pathnode, `/secrets/groups/${groupPath}`]);
}
} else {
newcrumbs.push(["Ungrouped", "/secrets/groups/"]);
}
if (secret) {
newcrumbs.push([secret, `/secrets/secret/${secret}`]);
}
for (let i = 0; i < newcrumbs.length; i++) {
let crumbParam = newcrumbs[i];
let newcrumb = createCrumb(crumbParam[0], crumbParam[1]);
breadcrumbs.appendChild(newcrumb);
}
}
function addTreeListener() {
const tree = document.querySelector("sl-tree");
if (!tree) return;
@ -33,4 +90,12 @@ document.addEventListener("DOMContentLoaded", () => {
});
}
});
}
document.addEventListener("DOMContentLoaded", () => {
addTreeListener();
});
document.addEventListener("htmx:afterSwap", () => {
addTreeListener();
});

View File

@ -42,6 +42,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
page=page, limit=per_page, total=audit_log.total, offset=offset
)
operations = list(Operation)
breadcrumbs = [("Audit", "/audit/")]
if request.headers.get("HX-Request"):
return templates.TemplateResponse(
request,
@ -56,7 +57,8 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
request,
"audit/index.html.j2",
{
"page_title": "Audit",
"page_title": "Audit Log",
"breadcrumbs": breadcrumbs,
"entries": audit_log.results,
"user": current_user,
"page_info": page_info,

View File

@ -93,42 +93,14 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
page=page, limit=per_page, total=results.total_results, offset=offset
)
breadcrumbs = [("clients", "/clients/")]
LOG.info("Results %r", results)
return templates.TemplateResponse(
request,
"clients/index.html.j2",
{
"page_title": "Clients",
"offset": offset,
"pages": paginate,
"clients": results.clients,
"user": current_user,
"results": results,
},
)
@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",
{
"breadcrumbs": breadcrumbs,
"page_title": "Clients",
"offset": offset,
"pages": paginate,
@ -197,6 +169,11 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
)
template = "clients/client.html.j2"
breadcrumbs = [
("clients", "/clients/"),
(results.client.name, request.url.path),
]
headers: dict[str, str] = {}
if request.headers.get("HX-Request"):
headers["HX-Push-Url"] = request.url.path
@ -207,6 +184,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
template,
{
"page_title": f"Client {results.client.name}",
"breadcrumbs": breadcrumbs,
"pages": results.pages,
"clients": results.results.clients,
"client": results.client,

View File

@ -81,7 +81,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
request,
"dashboard.html",
{
"page_title": "sshecret",
"page_title": "Dashboard",
"user": current_user,
"stats": stats,
"last_login_events": last_login_events,

View File

@ -63,12 +63,15 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
admin: Annotated[AdminBackend, Depends(dependencies.get_admin_backend)],
current_user: Annotated[LocalUserInfo, Depends(dependencies.get_user_info)],
):
breadcrumbs = [("secrets", "/secrets/")]
groups = await admin.get_secret_groups()
return templates.TemplateResponse(
request,
"secrets/index.html.j2",
{
"page_title": "Secrets",
"groups": groups,
"breadcrumbs": breadcrumbs,
"user": current_user,
"selected_group": None,
"group_path_nodes": ["/"],
@ -83,8 +86,15 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
):
"""Show the root path."""
clients = await admin.get_clients()
breadcrumbs = [
("secrets", "/secrets/"),
("groups", "/secrets/groups/"),
("Ungrouped", "/secrets/groups/"),
]
context: dict[str, Any] = {
"clients": clients,
"breadcrumbs": breadcrumbs,
"root_group_page": True,
}
headers: dict[str, str] = {}
@ -95,6 +105,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
else:
groups = await admin.get_secret_groups()
template_name = "secrets/index.html.j2"
context["page_title"] = "Secrets"
context["user"] = current_user
context["groups"] = groups
context["group_path_nodes"] = ["/"]
@ -119,11 +130,20 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
)
clients = await admin.get_clients()
breadcrumbs = [("secrets", "/secrets/"), ("groups", "/secrets/groups/")]
path_nodes = group.path.split("/")
for x in range(len(path_nodes)):
next_node = x + 1
group_path = "/".join(path_nodes[:next_node])
crumb_path = os.path.join("/secrets", group_path)
breadcrumbs.append((path_nodes[x], crumb_path))
headers: dict[str, str] = {}
context: dict[str, Any] = {
"group_page": True,
"group": group,
"clients": clients,
"breadcrumbs": breadcrumbs,
}
if request.headers.get("HX-Request"):
# This is a HTMX request.
@ -133,6 +153,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
template_name = "secrets/index.html.j2"
groups = await admin.get_secret_groups()
context["page_title"] = "Secrets"
context["user"] = current_user
context["groups"] = groups
context["group_path_nodes"] = group.path.split("/")
@ -180,6 +201,7 @@ def create_router(dependencies: FrontendDependencies) -> APIRouter:
group_path = group.path.split("/")
template_name = "secrets/index.html.j2"
context["page_title"] = "Secrets"
context["user"] = current_user
context["groups"] = groups
context["group_path_nodes"] = group_path

View File

@ -382,9 +382,6 @@
.order-1 {
order: 1;
}
.order-2 {
order: 2;
}
.col-span-2 {
grid-column: span 2 / span 2;
}
@ -457,9 +454,6 @@
.ms-3 {
margin-inline-start: calc(var(--spacing) * 3);
}
.ms-auto {
margin-inline-start: auto;
}
.me-2 {
margin-inline-end: calc(var(--spacing) * 2);
}
@ -682,15 +676,18 @@
.h-\[12px\] {
height: 12px;
}
.h-\[16px\] {
height: 16px;
}
.h-\[32px\] {
height: 32px;
}
.h-\[36rem\] {
height: 36rem;
}
.h-\[calc\(100vh-4rem\)\] {
height: calc(100vh - 4rem);
}
.h-\[calc\(100vh-8rem\)\] {
height: calc(100vh - 8rem);
}
.h-\[calc\(100vh-10rem\)\] {
height: calc(100vh - 10rem);
}
.h-full {
height: 100%;
}
@ -1245,9 +1242,6 @@
.border-blue-300 {
border-color: var(--color-blue-300);
}
.border-blue-700 {
border-color: var(--color-blue-700);
}
.border-gray-100 {
border-color: var(--color-gray-100);
}
@ -1332,9 +1326,6 @@
.bg-blue-600 {
background-color: var(--color-blue-600);
}
.bg-blue-700 {
background-color: var(--color-blue-700);
}
.bg-emerald-500 {
background-color: var(--color-emerald-500);
}
@ -2175,13 +2166,6 @@
}
}
}
.hover\:bg-blue-800 {
&:hover {
@media (hover: hover) {
background-color: var(--color-blue-800);
}
}
}
.hover\:bg-gray-50 {
&:hover {
@media (hover: hover) {
@ -2774,11 +2758,6 @@
inset: calc(var(--spacing) * 0);
}
}
.md\:order-1 {
@media (width >= 48rem) {
order: 1;
}
}
.md\:order-2 {
@media (width >= 48rem) {
order: 2;
@ -2859,6 +2838,16 @@
width: calc(var(--spacing) * 64);
}
}
.md\:w-72 {
@media (width >= 48rem) {
width: calc(var(--spacing) * 72);
}
}
.md\:w-80 {
@media (width >= 48rem) {
width: calc(var(--spacing) * 80);
}
}
.md\:w-\[calc\(100\%-256px\)\] {
@media (width >= 48rem) {
width: calc(100% - 256px);
@ -3114,6 +3103,11 @@
width: auto;
}
}
.lg\:table-fixed {
@media (width >= 64rem) {
table-layout: fixed;
}
}
.lg\:grid-cols-2 {
@media (width >= 64rem) {
grid-template-columns: repeat(2, minmax(0, 1fr));
@ -3469,11 +3463,6 @@
border-color: var(--color-red-800);
}
}
.dark\:bg-blue-600 {
&:where(.dark, .dark *) {
background-color: var(--color-blue-600);
}
}
.dark\:bg-blue-900 {
&:where(.dark, .dark *) {
background-color: var(--color-blue-900);
@ -3728,15 +3717,6 @@
}
}
}
.dark\:hover\:bg-blue-700 {
&:where(.dark, .dark *) {
&:hover {
@media (hover: hover) {
background-color: var(--color-blue-700);
}
}
}
}
.dark\:hover\:bg-blue-800 {
&:where(.dark, .dark *) {
&:hover {

View File

@ -19,3 +19,11 @@ sl-details.small-details::part(header) {
sl-details.small-details::part(base) {
font-size: var(--sl-input-font-size-small);
}
@media (prefers-color-scheme: dark) {
sl-details::part(base) {
background-color: var(--color-gray-700);
border: solid 1px var(--color-gray-500);
color: var(--color-gray-50);
}
}