mirror of
https://github.com/dalbodeule/snap-admin.git
synced 2025-08-12 14:51:13 +00:00
0.0.1 Alpha version
This commit is contained in:
74
src/main/resources/templates/fragments/data_row.html
Normal file
74
src/main/resources/templates/fragments/data_row.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head></head>
|
||||
<body>
|
||||
<tr th:fragment="data_row(row, selectable)" class="table-data-row">
|
||||
<td th:if=${selectable} class="table-checkbox">
|
||||
<input type="checkbox" class="form-check-input" name="ids"
|
||||
th:value="${row.getPrimaryKeyValue()}" form="delete-form">
|
||||
</td>
|
||||
<td th:each="field : ${schema.getSortedFields()}">
|
||||
<th:block th:if="${!row.has(field)}">
|
||||
<span class="font-monospace null-label">NULL</span>
|
||||
</th:block>
|
||||
<th:block th:if="${row.has(field)}">
|
||||
<th:block th:replace="~{fragments/data_row :: data_row_field(field=${field}, object=${row})}"></th:block>
|
||||
</th:block>
|
||||
</td>
|
||||
|
||||
<td th:each="colName : ${schema.getComputedColumnNames()}">
|
||||
<span th:text="${row.compute(colName)}"></span>
|
||||
</td>
|
||||
|
||||
<td class="text-center row-icons" th:if="${selectable}">
|
||||
<a class="ps-1" th:href="|/dbadmin/model/${schema.getJavaClass().getName()}/edit/${row.getPrimaryKeyValue()}|">
|
||||
<i class="bi bi-pencil-square"></i></a>
|
||||
<form class="delete-form" method="POST"
|
||||
th:action="|/dbadmin/model/${schema.getJavaClass().getName()}/delete/${row.getPrimaryKeyValue()}|">
|
||||
<button><i class="bi bi-trash"></i></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<!-- data-row-field fragment -->
|
||||
<th:block th:fragment="data_row_field(field, object)">
|
||||
<th:block th:if="${field.getConnectedType() != null}">
|
||||
<a th:href="|/dbadmin/model/${field.getConnectedType().getName()}/show/${object.traverse(field).getPrimaryKeyValue()}|">
|
||||
<span th:text="${object.has(field) ? object.traverse(field).getPrimaryKeyValue() : 'NULL'}"></span>
|
||||
</a>
|
||||
<p class="p-0 m-0"
|
||||
th:text="${object.traverse(field).getDisplayName()}"></p>
|
||||
</th:block>
|
||||
<th:block th:if="${field.getConnectedType() == null}">
|
||||
<th:block th:if="${field.isPrimaryKey()}">
|
||||
<a th:href="|/dbadmin/model/${schema.getClassName()}/show/${object.get(field).getValue()}|">
|
||||
<span th:text="${object.get(field).getFormattedValue()}">
|
||||
</span>
|
||||
</a>
|
||||
</th:block>
|
||||
<th:block th:if="${!field.isPrimaryKey()}">
|
||||
<span th:text="${object.get(field).getFormattedValue()}" th:if="${!field.isBinary()}">
|
||||
</span>
|
||||
<span th:unless="${!field.isBinary()}">
|
||||
<th:block th:if="${object.get(field).getValue()}">
|
||||
<a class="text-decoration-none null-label"
|
||||
th:href="|/dbadmin/download/${schema.getJavaClass().getName()}/${field.getName()}/${object.get(schema.getPrimaryKey()).getValue()}|">
|
||||
<i class="align-middle bi bi-box-arrow-down"></i><span class="align-middle"> Download</span>
|
||||
</a>
|
||||
</th:block>
|
||||
<th:block th:unless="${object.get(field).getValue()}">
|
||||
<span class="font-monospace null-label">NULL</span>
|
||||
</th:block>
|
||||
|
||||
</span>
|
||||
</th:block>
|
||||
</th:block>
|
||||
</th:block>
|
||||
<!-- end data-row-field fragment -->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
42
src/main/resources/templates/fragments/forms.html
Normal file
42
src/main/resources/templates/fragments/forms.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head></head>
|
||||
<body>
|
||||
<div class="autocomplete-input position-relative" th:fragment="input_autocomplete(field, value)">
|
||||
<input class="autocomplete form-control" type="text" th:name="${field.getName()}"
|
||||
th:data-classname="${field.getConnectedType().getName()}"
|
||||
autocomplete="off"
|
||||
th:value="${value}"
|
||||
placeholder="NULL">
|
||||
</input>
|
||||
<div class="suggestions d-none">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="autocomplete-multi-input position-relative" th:fragment="input_autocomplete_multi(field, values)">
|
||||
<div class="position-relative">
|
||||
<input class="autocomplete form-control" type="text"
|
||||
autocomplete="off"
|
||||
th:name="|${field.getName()}[]|"
|
||||
th:data-fieldname="|${field.getName()}[]|"
|
||||
th:data-classname="${field.getConnectedType().getName()}">
|
||||
</input>
|
||||
<div class="suggestions d-none">
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge bg-danger mb-0 mt-2 value-badge clear-all-badge"
|
||||
th:classAppend="${values == null || values.isEmpty() ? 'd-none' : ''}">Clear all <i class="bi bi-trash"></i></span>
|
||||
<div class="mt-0 mb-2 selected-values">
|
||||
<th:block th:each="value : ${values}" th:if="${values}">
|
||||
<span class="value-badge">
|
||||
<input type="checkbox" checked="checked" class="badge-checkbox"
|
||||
th:name="|${field.getName()}[]|" th:value="${value.getPrimaryKeyValue()}">
|
||||
<span class="badge bg-primary me-2" th:text="${value.getDisplayName()}">
|
||||
</span>
|
||||
</span>
|
||||
</th:block>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
186
src/main/resources/templates/fragments/resources.html
Normal file
186
src/main/resources/templates/fragments/resources.html
Normal file
@@ -0,0 +1,186 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head th:fragment="head">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css" integrity="sha384-b6lVK+yci+bfDmaY1u0zE8YYJt0TZxLEAFyYSLHId4xoVvsrQu3INevFKo+Xir8e" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/css/dbadmin.css">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.min.js" integrity="sha512-1/RvZTcCDEUjY/CypiMz+iqqtaoQfAITmNSJY17Myp4Ms5mdxPS5UV7iOfdZoxcGhzFbOm6sntTKJppjvuhg4g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script type="text/javascript" src="/js/table.js"></script>
|
||||
<script type="text/javascript" src="/js/autocomplete.js"></script>
|
||||
<script type="text/javascript" src="/js/autocomplete-multi.js"></script>
|
||||
<title th:text="${title != null ? title + ' | Spring Boot DB Admin Panel' : 'Spring Boot DB Admin Panel'}"></title>
|
||||
</head>
|
||||
|
||||
<th:block th:fragment="alerts">
|
||||
<div th:if="${message}" class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<p th:text="${message}" class="mb-0"></p>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<div th:if="${error}" class="alert alert-danger">
|
||||
<h6 class="fw-bold" th:if="${errorTitle}" th:text="${errorTitle}"></h6>
|
||||
<p class="mb-0" th:text="${error}"></p>
|
||||
</div>
|
||||
</th:block>
|
||||
|
||||
<nav class="navbar fixed-top navbar-expand-lg bg-accent color-white" th:fragment="navbar">
|
||||
<div class="container-fluid">
|
||||
<a class=" fw-bold navbar-brand" href="/"><i class="bi bi-hexagon-fill"></i> Spring Boot DB Admin Panel</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="sidebar bg-lightest position-relative" th:fragment="sidebar(page)">
|
||||
<div class="sidebar-top">
|
||||
<h6 class="fw-bold pt-2 ms-3 menu-subheading d-none d-md-block">MENU</h6>
|
||||
<ul class="sidebar-menu pb-0 mb-0 ">
|
||||
<li th:class="${#strings.equals(activePage, 'home') ? 'active' : ''}">
|
||||
<a href="/dbadmin">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="menu-icon">
|
||||
<i class="bi bi-house"></i>
|
||||
</div>
|
||||
<div class="menu-entry-text d-none d-md-block">
|
||||
Home
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li th:class="${#strings.equals(activePage, 'entities') ? 'active' : ''}">
|
||||
<a href="/dbadmin">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="menu-icon">
|
||||
<i class="bi bi-database"></i>
|
||||
</div>
|
||||
<div class="menu-entry-text d-none d-md-block">
|
||||
Entities
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li th:class="${#strings.equals(activePage, 'console') ? 'active' : ''}">
|
||||
<a href="/live">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="menu-icon">
|
||||
<i class="bi bi-terminal"></i>
|
||||
</div>
|
||||
<div class="menu-entry-text d-none d-md-block">
|
||||
SQL Console
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li th:class="${#strings.equals(activePage, 'ai') ? 'active' : ''}">
|
||||
<a href="/search">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="menu-icon">
|
||||
<i class="bi bi-share"></i>
|
||||
</div>
|
||||
<div class="menu-entry-text d-none d-md-block">
|
||||
AI console
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li th:class="${#strings.equals(activePage, 'settings') ? 'active' : ''}">
|
||||
<a href="/dbadmin/settings">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="menu-icon">
|
||||
<i class="bi bi-gear"></i>
|
||||
</div>
|
||||
<div class="menu-entry-text d-none d-md-block">
|
||||
Settings
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- <div class="separator"></div> -->
|
||||
</div>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="separator"></div>
|
||||
<ul class="sidebar-menu mb-0 pb-0">
|
||||
<li th:class="${#strings.equals(page, 'about') ? 'active' : ''}">
|
||||
<a href="/about">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="menu-icon">
|
||||
<i class="bi bi-question-circle"></i>
|
||||
</div>
|
||||
<div class="menu-entry-text d-none d-md-block">
|
||||
About
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav aria-label="Results pagination" th:fragment="pagination(page)">
|
||||
<div class="d-flex justify-content-between">
|
||||
|
||||
<div th:if="${page != null && page.getPagination().getMaxPage() != 1}" class="d-flex">
|
||||
<ul class="pagination me-3">
|
||||
<li class="page-item" th:if="${page.getPagination().getCurrentPage() != 1}">
|
||||
<a class="page-link" th:href="@{|/dbadmin/model/${schema.getClassName()}|(query=${query},page=${page.getPagination().getCurrentPage() - 1},pageSize=${page.getPagination().getPageSize()})}" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="page-item" th:each="p : ${page.getPagination().getBeforePages()}">
|
||||
<a class="page-link" th:href="@{|/dbadmin/model/${schema.getClassName()}|(query=${query},page=${p},pageSize=${page.getPagination().getPageSize()})}" th:text="${p}"></a>
|
||||
</li>
|
||||
|
||||
<li class="page-item active">
|
||||
<a class="page-link" href="#" th:text="${page.getPagination().getCurrentPage()}"></a>
|
||||
</li>
|
||||
|
||||
<li class="page-item" th:each="p : ${page.getPagination().getAfterPages()}">
|
||||
<a class="page-link" th:href="@{|/dbadmin/model/${schema.getClassName()}|(query=${query},page=${p},pageSize=${page.getPagination().getPageSize()})}" th:text="${p}"></a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link"
|
||||
th:if="${!page.getPagination().isLastPage()}"
|
||||
th:href="@{|/dbadmin/model/${schema.getClassName()}|(query=${query},page=${page.getPagination().getCurrentPage() + 1},pageSize=${page.getPagination().getPageSize()})}" aria-label="Next">
|
||||
<span class="sr-only">Next</span>
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="me-3">
|
||||
<form method="GET" th:action="@{|/dbadmin/model/${schema.getClassName()}|}">
|
||||
<input type="hidden" th:value="${page.getPagination().getCurrentPage()}" th:name="page">
|
||||
<input type="hidden" th:value="${query}" th:name="query">
|
||||
<input type="hidden" name="pageSize">
|
||||
<select class="form-select page-size">
|
||||
<option disabled>Page size</option>
|
||||
<option th:selected="${page.getPagination().getPageSize() == 50}">50</option>
|
||||
<option th:selected="${page.getPagination().getPageSize() == 100}">100</option>
|
||||
<option th:selected="${page.getPagination().getPageSize() == 150}">150</option>
|
||||
<option th:selected="${page.getPagination().getPageSize() == 200}">200</option>
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center" th:if="${page.getPagination().getMaxPage() > 1}">
|
||||
<p class="m-0 p-0">
|
||||
<i>Showing [[ ${page.getActualResults()} ]] of [[ ${page.getPagination().getMaxElement()} ]] results</i>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center" th:if="${page.getPagination().getMaxPage() == 1}">
|
||||
<p class="m-0 p-0">
|
||||
<i>Showing [[ ${page.getActualResults()} ]] of [[ ${page.getPagination().getMaxElement()} ]] results</i>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="bulk-actions">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</html>
|
50
src/main/resources/templates/fragments/table.html
Normal file
50
src/main/resources/templates/fragments/table.html
Normal file
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head></head>
|
||||
<body>
|
||||
<div th:fragment="table(results, schema)">
|
||||
<div th:if="${results == null || results.isEmpty()}">
|
||||
<p class="alert alert-warning">This table contains no data.</p>
|
||||
</div>
|
||||
<div th:if="${results != null && results.size() > 0}">
|
||||
<table class="table table-striped align-middle mt-3">
|
||||
<tr class="table-data-row">
|
||||
<th th:each="field : ${schema.getSortedFields()}">
|
||||
<div class="m-0 p-0 d-flex justify-content-between">
|
||||
<div class="column-title">
|
||||
<span th:if="${field.isPrimaryKey()}">
|
||||
<i title="Primary Key" class="bi bi-key"></i>
|
||||
</span>
|
||||
<span th:if="${field.isForeignKey()}">
|
||||
<i title="Foreign Key" class="bi bi-link"></i>
|
||||
</span>
|
||||
<span class="m-0 p-0" th:text="${field.getName()}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0 p-0 dbfieldtype"><small th:text="${field.getType()}"></small></p>
|
||||
</th>
|
||||
<th class="table-data-row" th:each="colName : ${schema.getComputedColumnNames()}">
|
||||
<div class="m-0 p-0 d-flex justify-content-between">
|
||||
<div class="column-title">
|
||||
<i title="Primary Key" class="bi bi-cpu"></i>
|
||||
<span class="m-0 p-0" th:text="${colName}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0 p-0 dbfieldtype"><small>COMPUTED</small></p>
|
||||
</th>
|
||||
</tr>
|
||||
<th:block th:each="r : ${results}">
|
||||
<tr th:replace="~{fragments/data_row :: data_row(row=${r}, selectable=${false})}">
|
||||
</tr>
|
||||
</th:block>
|
||||
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
77
src/main/resources/templates/fragments/table_selectable.html
Normal file
77
src/main/resources/templates/fragments/table_selectable.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
|
||||
<head></head>
|
||||
<body>
|
||||
<div class="table-selectable" th:fragment="table(results, schema)">
|
||||
<div th:if="${results.isEmpty()}">
|
||||
<p>This table contains no data.</p>
|
||||
</div>
|
||||
<div th:if="${results.size() > 0}">
|
||||
<form id="delete-form" th:action="|/dbadmin/model/${schema.getClassName()}/delete|" method="POST">
|
||||
</form>
|
||||
<nav th:replace="~{fragments/resources :: pagination(${page})}">
|
||||
</nav>
|
||||
<table class="table table-striped align-middle mt-3">
|
||||
<tr class="table-data-row">
|
||||
<th class="table-checkbox"><input type="checkbox" class="form-check-input check-all"></th>
|
||||
<th class="table-data-row" th:each="field : ${schema.getSortedFields()}">
|
||||
<div class="m-0 p-0 d-flex justify-content-between">
|
||||
<div class="column-title">
|
||||
<span th:if="${field.isPrimaryKey()}">
|
||||
<i title="Primary Key" class="bi bi-key"></i>
|
||||
</span>
|
||||
<span th:if="${field.isForeignKey()}">
|
||||
<i title="Foreign Key" class="bi bi-link"></i>
|
||||
</span>
|
||||
<span class="m-0 p-0" th:text="${field.getName()}"></span>
|
||||
</div>
|
||||
<div class="align-items-center">
|
||||
<h4 class="m-0" th:if="${page}">
|
||||
<th:block th:if="${sortKey != field.getName()}" >
|
||||
<a th:href="@{|/dbadmin/model/${schema.getClassName()}|(page=${page.getPagination().getCurrentPage()},
|
||||
pageSize=${page.getPagination().getPageSize()},sortKey=${field.getName()},sortOrder=DESC)}">
|
||||
<i title="Sort" class="bi bi-caret-up"></i>
|
||||
</a>
|
||||
</th:block>
|
||||
<th:block th:unless="${sortKey != field.getName()}">
|
||||
<a th:if="${sortOrder == 'DESC'}"
|
||||
th:href="@{|/dbadmin/model/${schema.getClassName()}|(page=${page.getPagination().getCurrentPage()},
|
||||
pageSize=${page.getPagination().getPageSize()},sortKey=${field.getName()},sortOrder=ASC)}">
|
||||
<i title="Sort" class="bi bi-caret-down-fill"></i>
|
||||
</a>
|
||||
<a th:if="${sortOrder == 'ASC'}"
|
||||
th:href="@{|/dbadmin/model/${schema.getClassName()}|(page=${page.getPagination().getCurrentPage()},
|
||||
pageSize=${page.getPagination().getPageSize()},sortKey=${field.getName()},sortOrder=DESC)}">
|
||||
<i title="Sort" class="bi bi-caret-up-fill"></i>
|
||||
</a>
|
||||
</th:block>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0 p-0 dbfieldtype"><small th:text="${field.getType()}"></small></p>
|
||||
</th>
|
||||
<th class="table-data-row" th:each="colName : ${schema.getComputedColumnNames()}">
|
||||
<div class="m-0 p-0 d-flex justify-content-between">
|
||||
<div class="column-title">
|
||||
<i title="Primary Key" class="bi bi-cpu"></i>
|
||||
<span class="m-0 p-0" th:text="${colName}"></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="m-0 p-0 dbfieldtype"><small>COMPUTED</small></p>
|
||||
</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<th:block th:each="r : ${results}">
|
||||
<tr th:replace="~{fragments/data_row :: data_row(row=${r},selectable=${true})}"></tr>
|
||||
</th:block>
|
||||
</table>
|
||||
<nav th:replace="~{fragments/resources :: pagination(${page})}">
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user