- Добавил активацию нового аккаунта по электронной почте.
https://www.youtube.com/watch?v=yBXs_gtSmUc&list=PLU2ftbIeotGpAYRP9Iv2KLIwK36-o_qYk&index=12
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -65,6 +65,10 @@
|
|||||||
<artifactId>spring-security-core</artifactId>
|
<artifactId>spring-security-core</artifactId>
|
||||||
<version>6.3.1</version>
|
<version>6.3.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class WebSecurityConfig {
|
|||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests((requests) -> requests
|
.authorizeHttpRequests((requests) -> requests
|
||||||
.requestMatchers("/", "/registration","/static/**").permitAll()
|
.requestMatchers("/", "/registration","/static/**", "/activate/*").permitAll()
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
)
|
||||||
.formLogin((form) -> form
|
.formLogin((form) -> form
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
package net.sytes.kashey.sweater.controller;
|
package net.sytes.kashey.sweater.controller;
|
||||||
|
|
||||||
import net.sytes.kashey.sweater.domain.Role;
|
|
||||||
import net.sytes.kashey.sweater.domain.User;
|
import net.sytes.kashey.sweater.domain.User;
|
||||||
import net.sytes.kashey.sweater.repository.UserRepository;
|
import net.sytes.kashey.sweater.repository.UserRepository;
|
||||||
|
import net.sytes.kashey.sweater.service.CustomUserDetailsService;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
public class RegistrationController {
|
public class RegistrationController {
|
||||||
|
private final CustomUserDetailsService customUserDetailsService;
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
public RegistrationController(CustomUserDetailsService customUserDetailsService) {
|
||||||
|
this.customUserDetailsService = customUserDetailsService;
|
||||||
public RegistrationController(UserRepository userRepository) {
|
|
||||||
this.userRepository = userRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/registration")
|
@GetMapping("/registration")
|
||||||
@@ -29,20 +29,24 @@ public class RegistrationController {
|
|||||||
@PostMapping("/registration")
|
@PostMapping("/registration")
|
||||||
public String addUser(@ModelAttribute User user, Map<String, Object> model) {
|
public String addUser(@ModelAttribute User user, Map<String, Object> model) {
|
||||||
|
|
||||||
if (user.getUsername() == null || user.getUsername().isEmpty() || user.getUsername().isBlank()) {
|
String result = customUserDetailsService.addUser(user);
|
||||||
model.put("message", "User name must not be empty");
|
if (!result.isEmpty()) {
|
||||||
|
model.put("message", result);
|
||||||
return "registration";
|
return "registration";
|
||||||
}
|
} else {
|
||||||
|
|
||||||
if (userRepository.findByUsername(user.getUsername()).isPresent()) {
|
|
||||||
model.put("message", "User already exists");
|
|
||||||
return "registration";
|
|
||||||
}
|
|
||||||
|
|
||||||
user.setEnabled(true);
|
|
||||||
user.setRoles(Collections.singleton(Role.USER));
|
|
||||||
user.setPassword("{noop}" + user.getPassword()); //пока не введено шифрование паролей
|
|
||||||
userRepository.save(user);
|
|
||||||
return "redirect:/login";
|
return "redirect:/login";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/activate/{activateCode}")
|
||||||
|
public String activateAccount(Model model, @PathVariable String activateCode) {
|
||||||
|
|
||||||
|
if (customUserDetailsService.activateUser(activateCode)) {
|
||||||
|
model.addAttribute("message", "User successfully activated!");
|
||||||
|
} else {
|
||||||
|
model.addAttribute("message", "User activation is failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return "login";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ public class User {
|
|||||||
private String username;
|
private String username;
|
||||||
private String password;
|
private String password;
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
private String activationCode;
|
||||||
@ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
|
@ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
|
||||||
@CollectionTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"))
|
@CollectionTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"))
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
@@ -70,4 +74,20 @@ public class User {
|
|||||||
public void setRoles(Set<Role> roles) {
|
public void setRoles(Set<Role> roles) {
|
||||||
this.roles = roles;
|
this.roles = roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActivationCode() {
|
||||||
|
return activationCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActivationCode(String activationCode) {
|
||||||
|
this.activationCode = activationCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,6 @@ import java.util.Optional;
|
|||||||
@Repository
|
@Repository
|
||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
Optional<User> findByUsername(String name);
|
Optional<User> findByUsername(String name);
|
||||||
|
|
||||||
|
User findByActivationCode(String activationCode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,32 @@
|
|||||||
package net.sytes.kashey.sweater.service;
|
package net.sytes.kashey.sweater.service;
|
||||||
|
|
||||||
|
import net.sytes.kashey.sweater.domain.Role;
|
||||||
import net.sytes.kashey.sweater.domain.User;
|
import net.sytes.kashey.sweater.domain.User;
|
||||||
import net.sytes.kashey.sweater.repository.UserRepository;
|
import net.sytes.kashey.sweater.repository.UserRepository;
|
||||||
import net.sytes.kashey.sweater.security.CustomUserDetails;
|
import net.sytes.kashey.sweater.security.CustomUserDetails;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CustomUserDetailsService implements UserDetailsService {
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
private final EmailService emailService;
|
||||||
|
|
||||||
public CustomUserDetailsService(UserRepository userRepository) {
|
@Value("${hosting.path}")
|
||||||
|
private String hostingPath;
|
||||||
|
|
||||||
|
public CustomUserDetailsService(UserRepository userRepository, EmailService emailService) {
|
||||||
|
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
|
this.emailService = emailService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -25,4 +36,48 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + name));
|
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + name));
|
||||||
return new CustomUserDetails(user);
|
return new CustomUserDetails(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String addUser(User user) {
|
||||||
|
|
||||||
|
if (user.getUsername() == null || user.getUsername().isEmpty() || user.getUsername().isBlank()) {
|
||||||
|
return "User name must not be empty!";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userRepository.findByUsername(user.getUsername()).isPresent()) {
|
||||||
|
return "User already exists!";
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setEnabled(true);
|
||||||
|
user.setActivationCode(UUID.randomUUID().toString());
|
||||||
|
user.setRoles(Collections.singleton(Role.USER));
|
||||||
|
user.setPassword("{noop}" + user.getPassword()); //пока не введено шифрование паролей
|
||||||
|
userRepository.save(user);
|
||||||
|
|
||||||
|
if (!ObjectUtils.isEmpty(user.getEmail())) {
|
||||||
|
|
||||||
|
String message = String.format(
|
||||||
|
"Hello, %s! \n" +
|
||||||
|
"Welcome to Sweater. Please, visit next link: %s/activate/%s",
|
||||||
|
user.getUsername(),
|
||||||
|
hostingPath,
|
||||||
|
user.getActivationCode()
|
||||||
|
);
|
||||||
|
|
||||||
|
emailService.send(user.getEmail(), "Activation code", message);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean activateUser(String activationCode) {
|
||||||
|
|
||||||
|
User user = userRepository.findByActivationCode(activationCode);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setActivationCode(null);
|
||||||
|
userRepository.save(user);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package net.sytes.kashey.sweater.service;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.mail.SimpleMailMessage;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class EmailService {
|
||||||
|
private final JavaMailSender javaMailSender;
|
||||||
|
|
||||||
|
@Value("${spring.mail.username}")
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public EmailService(JavaMailSender javaMailSender) {
|
||||||
|
this.javaMailSender = javaMailSender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void send(String mailTo, String subject, String text) {
|
||||||
|
|
||||||
|
SimpleMailMessage mailMessage = new SimpleMailMessage();
|
||||||
|
mailMessage.setFrom(username);
|
||||||
|
mailMessage.setTo(mailTo);
|
||||||
|
mailMessage.setSubject(subject);
|
||||||
|
mailMessage.setText(text);
|
||||||
|
|
||||||
|
javaMailSender.send(mailMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,4 +7,10 @@ spring.freemarker.expose-request-attributes=true
|
|||||||
spring.freemarker.expose-session-attributes=true
|
spring.freemarker.expose-session-attributes=true
|
||||||
spring.freemarker.suffix=.ftl
|
spring.freemarker.suffix=.ftl
|
||||||
upload.path=/home/kashey/soft/sweater/uploads
|
upload.path=/home/kashey/soft/sweater/uploads
|
||||||
|
hosting.path=http://localhost:8080
|
||||||
|
spring.mail.host=smtp.yandex.ru
|
||||||
|
spring.mail.username=username@yandex.ru
|
||||||
|
spring.mail.password=pa$$word
|
||||||
|
spring.mail.port=465
|
||||||
|
spring.mail.protocol=smtps
|
||||||
|
mail.debug=true
|
||||||
106
src/main/resources/static/custom.css
Normal file
106
src/main/resources/static/custom.css
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
|
||||||
|
:root {
|
||||||
|
--yellow-1c: #ffe100; /* Основной желтый */
|
||||||
|
--light-yellow-1c: #FDF5C9; /* Светло-желтый фон */
|
||||||
|
--dark-1c: #333333; /* Темный цвет для текста */
|
||||||
|
--light-yellow-header-1c: #fbed9e;
|
||||||
|
--light-grey-1c:#f2f2f2;
|
||||||
|
--brand-red-1c: #B22222;
|
||||||
|
--begie: #F5F5DC;
|
||||||
|
--light-y:#F5F5F5;
|
||||||
|
--header: #F5DEB3;
|
||||||
|
--brown-1c: #663300;
|
||||||
|
--gray-1c: #CCCCCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!* Фон приложения *!*/
|
||||||
|
body {
|
||||||
|
background-color: var(--light-y) !important;
|
||||||
|
color: var(--dark-1c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Навигационная панель */
|
||||||
|
.navbar {
|
||||||
|
background-color: var(--gray-1c) !important;
|
||||||
|
color: var(--dark-1c) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*.navbar .nav-link {*/
|
||||||
|
/* color: var(--brown-1c) !important;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.navbar .nav-link:hover {*/
|
||||||
|
/* color: black !important;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/* Кнопки */
|
||||||
|
/*.btn-primary {*/
|
||||||
|
/* background-color: var(--yellow-1c) !important;*/
|
||||||
|
/* border-color: var(--yellow-1c) !important;*/
|
||||||
|
/* color: var(--dark-1c) !important;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.btn-primary:hover {*/
|
||||||
|
/* background-color: #E6C200 !important; !* Чуть темнее *!*/
|
||||||
|
/* border-color: #E6C200 !important;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*!* Карточки и панели *!*/
|
||||||
|
/*.card, .panel {*/
|
||||||
|
/* background-color: var(--white-1c) !important;*/
|
||||||
|
/* border: 1px solid var(--gray-1c) !important;*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*!* Таблицы *!*/
|
||||||
|
/*.table {*/
|
||||||
|
/* background-color: var(--white-1c);*/
|
||||||
|
/* color: var(--dark-1c);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.table thead th {*/
|
||||||
|
/* background-color: var(--yellow-1c);*/
|
||||||
|
/* color: var(--dark-1c);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.table-striped tbody tr:nth-of-type(odd) {*/
|
||||||
|
/* background-color: var(--gray-1c);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*!* Поля ввода *!*/
|
||||||
|
/*.form-control {*/
|
||||||
|
/* background-color: var(--white-1c);*/
|
||||||
|
/* border: 1px solid var(--gray-1c);*/
|
||||||
|
/* color: var(--dark-1c);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.form-control:focus {*/
|
||||||
|
/* border-color: var(--yellow-1c);*/
|
||||||
|
/* box-shadow: 0 0 0 0.2rem rgba(255, 215, 0, 0.5);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*!* Выделение чекбоксов *!*/
|
||||||
|
/*.form-check-input:checked {*/
|
||||||
|
/* background-color: var(--yellow-1c);*/
|
||||||
|
/* border-color: var(--yellow-1c);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*!* Бейджи *!*/
|
||||||
|
/*.badge-primary {*/
|
||||||
|
/* background-color: var(--yellow-1c);*/
|
||||||
|
/* color: var(--dark-1c);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*!* Тени для элементов *!*/
|
||||||
|
/*.box-shadow {*/
|
||||||
|
/* box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*!* Стили для названия бренда (Sweater) *!*/
|
||||||
|
/*.navbar-brand {*/
|
||||||
|
/* color: var(--brown-1c) !important; !* Темный цвет текста *!*/
|
||||||
|
/* font-weight: bold; !* Сделаем жирным *!*/
|
||||||
|
/*}*/
|
||||||
|
|
||||||
|
/*.navbar-brand:hover {*/
|
||||||
|
/* color: black !important; !* Черный при наведении *!*/
|
||||||
|
/*}*/
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
body {
|
|
||||||
color: #191970;
|
|
||||||
}
|
|
||||||
@@ -2,5 +2,8 @@
|
|||||||
<#import "parts/login.ftl" as l>
|
<#import "parts/login.ftl" as l>
|
||||||
|
|
||||||
<@com.page>
|
<@com.page>
|
||||||
|
<#if message??>
|
||||||
|
<div class="mb-3"> ${message} </div>
|
||||||
|
</#if>
|
||||||
<@l.login "/login" false/>
|
<@l.login "/login" false/>
|
||||||
</@com.page>
|
</@com.page>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<#import "parts/common.ftl" as com>
|
<#import "parts/common.ftl" as com>
|
||||||
<@com.page>
|
<@com.page>
|
||||||
|
<link href="/static/custom.css" rel="stylesheet">
|
||||||
<div class="input-group mb-3">
|
<div class="input-group mb-3">
|
||||||
<form method="get" action="/main" class="form-inline">
|
<form method="get" action="/main" class="form-inline">
|
||||||
<input type="text" name="tag" value="${tag}" placeholder="Tag to search">
|
<input type="text" name="tag" value="${tag}" placeholder="Tag to search">
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Sweater</title>
|
<title>Sweater</title>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
<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">
|
<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">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<#macro login path isRegistrationForm=false>
|
<#macro login path isRegistrationForm=false>
|
||||||
<form action="${path}" method="post" xmlns="http://www.w3.org/1999/html">
|
<form action="${path}" method="post" xmlns="http://www.w3.org/1999/html">
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<label class="col-sm-2 col-form-label"> User Name :</label>
|
<label class="col-sm-2 col-form-label"> User Name:</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<input type="text" class="form-control" name="username" placeholder="User name"/>
|
<input type="text" class="form-control" name="username" placeholder="User name"/>
|
||||||
</div>
|
</div>
|
||||||
@@ -12,10 +12,20 @@
|
|||||||
<input type="password" class="form-control" name="password" placeholder="Password">
|
<input type="password" class="form-control" name="password" placeholder="Password">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<#if isRegistrationForm>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<label class="col-sm-2 col-form-label"> E-mail:</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input type="email" class="form-control" name="email" placeholder="E-mail"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#if !isRegistrationForm>
|
<#if !isRegistrationForm>
|
||||||
<a href="/registration" class="btn btn-primary">Add new user</a>
|
<a href="/registration" class="btn btn-primary">Add new user</a>
|
||||||
</#if>
|
</#if>
|
||||||
<input class="btn btn-primary" type="submit" <#if isRegistrationForm> value="Create" <#else> value="Sign in"</#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}"/>
|
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
|
||||||
</form>
|
</form>
|
||||||
</#macro>
|
</#macro>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<#import "login.ftl" as l>
|
<#import "login.ftl" as l>
|
||||||
|
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
|
<link href="/static/custom.css" rel="stylesheet">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="/">Sweater</a>
|
<a class="navbar-brand" href="/">Sweater</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<div class="mb-1"> Add new user </div>
|
<div class="mb-1"> Add new user </div>
|
||||||
<br>
|
<br>
|
||||||
<#if message??>
|
<#if message??>
|
||||||
${message}
|
<div class="mb-3"> ${message} </div>
|
||||||
</#if>
|
</#if>
|
||||||
<@l.login "/registration" true/>
|
<@l.login "/registration" true/>
|
||||||
</@com.page>
|
</@com.page>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<@com.page>
|
<@com.page>
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<thead class="thead-dark">
|
<thead class="table-sub-heading-color">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
<th scope="col">Role</th>
|
<th scope="col">Role</th>
|
||||||
|
|||||||
Reference in New Issue
Block a user