- Добавил Bootstrap и настроил внешний вид форм приложения.

https://www.youtube.com/watch?v=a51jGwoTNmI&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=13
This commit is contained in:
Leonid
2025-02-13 15:23:23 +03:00
parent 8580d4cfa6
commit 27441efde7
12 changed files with 189 additions and 83 deletions

View File

@@ -1,5 +1,6 @@
package net.sytes.kashey.sweater.security;
import net.sytes.kashey.sweater.domain.Role;
import net.sytes.kashey.sweater.domain.User;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -56,4 +57,8 @@ public class CustomUserDetails implements UserDetails {
public User getUser() {
return user;
}
public boolean isAdmin() {
return user.getRoles().contains(Role.ADMIN);
}
}

View File

@@ -4,6 +4,7 @@ spring.datasource.username=user
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.freemarker.expose-request-attributes=true
spring.freemarker.expose-session-attributes=true
spring.freemarker.suffix=.ftl
upload.path=/home/kashey/soft/sweater/uploads

View File

@@ -1,7 +1,7 @@
<#import "parts/common.ftl" as com>
<@com.page>
<div>Hello there</div>
<a href="/main">Main page</a>
<br>
<a href="/users">Users page</a>
<h5>Hello, guest</h5>
<div>
This is a simple version of Twitter
</div>
</@com.page>

View File

@@ -2,7 +2,5 @@
<#import "parts/login.ftl" as l>
<@com.page>
Login page
<@l.login "/login" />
<a href="/registration">Add new user</a>
<@l.login "/login" false/>
</@com.page>

View File

@@ -1,44 +1,56 @@
<#import "parts/common.ftl" as com>
<#import "parts/login.ftl" as l>
<@com.page>
<div>
<@l.logout />
<span> <a href="/users">Users page</a> </span>
<div class="input-group mb-3">
<form method="get" action="/main" class="form-inline">
<input type="text" name="tag" value="${tag}" placeholder="Tag to search">
<button type="submit" class="btn btn-primary ml-2">Search</button>
</form>
</div>
<div>
<a class="btn btn-primary" data-toggle="collapse" href="#collapseExample" role="button" aria-expanded="false"
aria-controls="collapseExample">
Click to add message
</a>
<div class="collapse" id="collapseExample">
<div class="form-group mt-3">
<form method="post" enctype="multipart/form-data">
<input type="text" name="text" placeholder="Enter message" />
<input type="text" name="tag" placeholder="tag">
<input type="file" name="file">
<input type="hidden" name="_csrf" value="${_csrf.token}" />
<button type="submit">Add message</button>
<div class="form-group">
<input type="text" class="form-control" name="text" placeholder="Text"/>
</div>
<div class="form-group">
<input type="text" class="form-control" name="tag" placeholder="Tag">
</div>
<div class="form-group">
<div class="custom-file">
<input type="file" name="file" id="customFile">
<label class="custom-file-label" for="customFile">Choose file</label>
</div>
</div>
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
<div class="form-group">
<button type="submit" class="btn btn-primary">Add message</button>
</div>
</form>
</div>
<div>
<form method="get" action="/main">
Tag to filer
<br>
<input type="text" name="tag" value="${tag}">
<button>Find</button>
</form>
</div>
<div>Messages list</div>
<div class="card-columns">
<#list messages as message>
<div>
<b>${message.id}</b>
<span>${message.text}</span>
<i>${message.tag}</i>
<strong>${message.authorName}</strong>
<div>
<#if message.filename??>
<img src="/img/${message.filename}">
<img src="/img/${message.filename}" class="card-img-top">
</#if>
</div>
<div class="m-2">
<span>${message.text}</span>
<i>${message.tag}</i>
</div>
<div class="card-footer">
${message.authorName}
</div>
</div>
<#else>
No messages found
</#list>
<br>
</div>
</@com.page>

View File

@@ -5,10 +5,19 @@
<title>Sweater</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="/static/style.css" rel="stylesheet" type="text/css" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
</head>
<body>
<#include "navbar.ftl">
<div class="container mt-5">
<#nested>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.14.3/dist/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</body>
</html>

View File

@@ -1,15 +1,28 @@
<#macro login path>
<form action="${path}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign in"/></div>
<input type="hidden" name="_csrf" value="${_csrf.token}" />
<#macro login path isRegistrationForm=false>
<form action="${path}" method="post" xmlns="http://www.w3.org/1999/html">
<div class="row mb-3">
<label class="col-sm-2 col-form-label"> User Name :</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="username" placeholder="User name"/>
</div>
</div>
<div class="row mb-3">
<label for="inputPassword3" class="col-sm-2 col-form-label"> Password: </label>
<div class="col-sm-6">
<input type="password" class="form-control" name="password" placeholder="Password">
</div>
</div>
<#if !isRegistrationForm>
<a href="/registration" class="btn btn-primary">Add new user</a>
</#if>
<input class="btn btn-primary" type="submit" <#if isRegistrationForm> value="Create" <#else> value="Sign in"</#if>/>
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
</form>
</#macro>
<#macro logout>
<form action="/logout" method="post">
<input type="hidden" name="_csrf" value="${_csrf.token}" />
<input type="submit" value="Sign Out"/>
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
<button type="submit" class="btn btn-primary">Sign Out</button>
</form>
</#macro>

View File

@@ -0,0 +1,29 @@
<#include "security.ftl">
<#import "login.ftl" as l>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="/">Sweater</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 class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/main">Messages</a>
</li>
<#if isAdmin>
<li class="nav-item">
<a class="nav-link" aria-current="page" href="/users">Users</a>
</li>
</#if>
</ul>
</div>
<div class="navbar-text mr-3" >${name}</div>
<div><#if name != "unknown"> <@l.logout /> </#if></div>
</div>
</nav>

View File

@@ -0,0 +1,16 @@
<#assign
known = SPRING_SECURITY_CONTEXT??
>
<#if known>
<#assign
user = SPRING_SECURITY_CONTEXT.authentication.principal
name = user.getUsername()
isAdmin = user.isAdmin()
>
<#else>
<#assign
name = "unknown"
isAdmin = false
>
</#if>

View File

@@ -2,10 +2,10 @@
<#import "parts/login.ftl" as l>
<@com.page>
Registration page
<div class="mb-1"> Add new user </div>
<br>
<#if message??>
${message}
</#if>
<@l.login "/registration" />
<@l.login "/registration" true/>
</@com.page>

View File

@@ -1,14 +1,30 @@
<#import "parts/common.ftl" as com>
<@com.page>
<form action="/users" method="post">
<input type="text" name="username" value="${user.username}">
<div class="container mt-4">
<form action="/users" method="post" class="needs-validation" novalidate>
<div class="form-group">
<label for="username">Username:</label>
<input type="text" class="form-control" id="username" name="username" value="${user.username}" required>
<div class="invalid-feedback">
Please provide a username.
</div>
</div>
<div class="form-group">
<label>Roles:</label>
<#list roles as role>
<div>
<label><input type="checkbox" name="${role}" ${user.roles?seq_contains(role)?string("checked", "")}>${role}</label>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="${role}"
id="${role}" ${user.roles?seq_contains(role)?string("checked", "")}>
<label class="form-check-label" for="${role}">
${role}
</label>
</div>
</#list>
</div>
<input type="hidden" value="${user.id}" name="userId">
<input type="hidden" value="${_csrf.token}" name="_csrf">
<button type="submit">Save</button>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</@com.page>

View File

@@ -1,23 +1,30 @@
<#import "parts/common.ftl" as com>
<@com.page>
<div>Users</div>
<table>
<thead>
<div class="container mt-4">
<table class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<th>Name</th>
<th>Role</th>
<th></th>
<th scope="col">Name</th>
<th scope="col">Role</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<#list users as user>
<tr>
<td>${user.username}</td>
<td><#list user.roles as role>${role}<#sep>, </#list></td>
<td><a href="/users/${user.id}">edit</a></td>
<td>
<#list user.roles as role>
<span class="badge badge-primary">${role}</span>
<#sep> </#list>
</td>
<td>
<a href="/users/${user.id}" class="btn btn-sm btn-outline-primary">Edit</a>
</td>
</tr>
</#list>
</tbody>
</table>
<a href="/main">Main page</a>
<a href="/main" class="btn btn-primary">Main page</a>
</div>
</@com.page>