mirror of
https://git.imnavajas.es/jjimenez/printhub.git
synced 2026-01-12 16:38:46 +00:00
añadido gestion de usuarios
This commit is contained in:
6
pom.xml
6
pom.xml
@ -67,6 +67,12 @@
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
package com.printhub.printhub.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class PasswordEncoderConfig {
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.printhub.printhub.config;
|
||||
|
||||
import com.printhub.printhub.service.configuration.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.*;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.*;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/login", "/register", "/assets/**", "/api/lang", "/api/lang/**").permitAll()
|
||||
.anyRequest().authenticated())
|
||||
.userDetailsService(userService)
|
||||
.formLogin(login -> login
|
||||
.loginPage("/login")
|
||||
.loginProcessingUrl("/login")
|
||||
.defaultSuccessUrl("/", true)
|
||||
.failureUrl("/login?error=true")
|
||||
.permitAll())
|
||||
.logout(logout -> logout
|
||||
.logoutUrl("/logout")
|
||||
.logoutSuccessHandler((request, response, authentication) -> {
|
||||
String lang = request.getParameter("lang");
|
||||
if (lang == null || lang.isBlank()) lang = "es";
|
||||
response.sendRedirect("/login?logout&lang=" + lang);
|
||||
}));
|
||||
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig)
|
||||
throws Exception {
|
||||
return authConfig.getAuthenticationManager();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package com.printhub.printhub.controller.config;
|
||||
|
||||
import com.printhub.printhub.entity.configuration.User;
|
||||
import com.printhub.printhub.repository.configuration.UserRepository;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@ControllerAdvice
|
||||
public class CurrentUserAdvice {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@ModelAttribute("currentUser")
|
||||
public User getCurrentUser() {
|
||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
if (principal instanceof UserDetails userDetails) {
|
||||
return userRepository.findByUsername(userDetails.getUsername()).orElse(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
package com.printhub.printhub.controller.configuration;
|
||||
|
||||
import com.printhub.printhub.service.configuration.UserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
import com.printhub.printhub.entity.configuration.User;
|
||||
import com.printhub.printhub.repository.configuration.UserRepository;
|
||||
|
||||
@Controller
|
||||
public class AuthController {
|
||||
|
||||
private UserService userService;
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
public AuthController(UserRepository userRepository, UserService userService) {
|
||||
this.userRepository = userRepository;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
public String register(@RequestParam String username,
|
||||
@RequestParam String password,
|
||||
@RequestParam(defaultValue = "USER") String role) {
|
||||
userService.register(username, password, role);
|
||||
return "Usuario registrado correctamente.";
|
||||
}
|
||||
|
||||
@GetMapping("/login")
|
||||
public String login() {
|
||||
return "printhub/login";
|
||||
}
|
||||
|
||||
@ModelAttribute("currentUser")
|
||||
public User currentUser() {
|
||||
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
|
||||
if (principal instanceof org.springframework.security.core.userdetails.User springUser) {
|
||||
return userRepository.findByUsername(springUser.getUsername())
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import com.printhub.printhub.model.Breadcrum;
|
||||
import com.printhub.printhub.helper.Breadcrum;
|
||||
import com.printhub.printhub.service.configuration.PrinterService;
|
||||
import com.printhub.printhub.utils.datatables.DataTableRequest;
|
||||
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
package com.printhub.printhub.entity.configuration;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "customers")
|
||||
public class Customer {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
private String alias;
|
||||
private String cif;
|
||||
private String address;
|
||||
private String city;
|
||||
private String zipCode;
|
||||
private String country;
|
||||
private String phone;
|
||||
private String email;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private LocalDateTime deletedAt;
|
||||
|
||||
// Getters y Setters
|
||||
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getName() { return name; }
|
||||
public void setName(String name) { this.name = name; }
|
||||
|
||||
public String getAlias() { return alias; }
|
||||
public void setAlias(String alias) { this.alias = alias; }
|
||||
|
||||
public String getCif() { return cif; }
|
||||
public void setCif(String cif) { this.cif = cif; }
|
||||
|
||||
public String getAddress() { return address; }
|
||||
public void setAddress(String address) { this.address = address; }
|
||||
|
||||
public String getCity() { return city; }
|
||||
public void setCity(String city) { this.city = city; }
|
||||
|
||||
public String getZipCode() { return zipCode; }
|
||||
public void setZipCode(String zipCode) { this.zipCode = zipCode; }
|
||||
|
||||
public String getCountry() { return country; }
|
||||
public void setCountry(String country) { this.country = country; }
|
||||
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
|
||||
public String getEmail() { return email; }
|
||||
public void setEmail(String email) { this.email = email; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
|
||||
public LocalDateTime getDeletedAt() { return deletedAt; }
|
||||
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.printhub.printhub.model.configuration;
|
||||
package com.printhub.printhub.entity.configuration;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
package com.printhub.printhub.entity.configuration;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private String username;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String password;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String role; // Ej: ROLE_USER, ROLE_ADMIN
|
||||
|
||||
@Column(nullable = false)
|
||||
private boolean enabled = true;
|
||||
|
||||
// Campos adicionales
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
|
||||
@Column(columnDefinition = "TEXT")
|
||||
private String comments;
|
||||
|
||||
private LocalDateTime lastActive;
|
||||
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private LocalDateTime deletedAt;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "customer_id")
|
||||
private Customer customer;
|
||||
|
||||
// Getters y Setters
|
||||
|
||||
public Long getId() { return id; }
|
||||
|
||||
public void setId(Long id) { this.id = id; }
|
||||
|
||||
public String getUsername() { return username; }
|
||||
|
||||
public void setUsername(String username) { this.username = username; }
|
||||
|
||||
public String getPassword() { return password; }
|
||||
|
||||
public void setPassword(String password) { this.password = password; }
|
||||
|
||||
public String getRole() { return role; }
|
||||
|
||||
public void setRole(String role) { this.role = role; }
|
||||
|
||||
public boolean isEnabled() { return enabled; }
|
||||
|
||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
|
||||
public String getFirstName() { return firstName; }
|
||||
|
||||
public void setFirstName(String firstName) { this.firstName = firstName; }
|
||||
|
||||
public String getLastName() { return lastName; }
|
||||
|
||||
public void setLastName(String lastName) { this.lastName = lastName; }
|
||||
|
||||
public String getComments() { return comments; }
|
||||
|
||||
public void setComments(String comments) { this.comments = comments; }
|
||||
|
||||
public LocalDateTime getLastActive() { return lastActive; }
|
||||
|
||||
public void setLastActive(LocalDateTime lastActive) { this.lastActive = lastActive; }
|
||||
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
|
||||
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||
|
||||
public LocalDateTime getDeletedAt() { return deletedAt; }
|
||||
|
||||
public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
|
||||
|
||||
public Customer getCustomer() { return customer; }
|
||||
|
||||
public void setCustomer(Customer customer) { this.customer = customer; }
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.printhub.printhub.model;
|
||||
package com.printhub.printhub.helper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -1,9 +1,10 @@
|
||||
package com.printhub.printhub.repository.configuration;
|
||||
|
||||
import com.printhub.printhub.model.configuration.Printer;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
import com.printhub.printhub.entity.configuration.Printer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PrinterRepository extends JpaRepository<Printer, Long>, JpaSpecificationExecutor<Printer> {
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
package com.printhub.printhub.repository.configuration;
|
||||
|
||||
import com.printhub.printhub.entity.configuration.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
Optional<User> findByUsername(String username);
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.printhub.printhub.security;
|
||||
|
||||
import com.printhub.printhub.entity.configuration.User;
|
||||
import com.printhub.printhub.repository.configuration.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Component
|
||||
public class AuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(AuthenticationSuccessEvent event) {
|
||||
Object principal = event.getAuthentication().getPrincipal();
|
||||
|
||||
if (principal instanceof UserDetails userDetails) {
|
||||
userRepository.findByUsername(userDetails.getUsername()).ifPresent(user -> {
|
||||
user.setLastActive(LocalDateTime.now());
|
||||
userRepository.save(user);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package com.printhub.printhub.service.configuration;
|
||||
|
||||
import com.printhub.printhub.model.configuration.Printer;
|
||||
import com.printhub.printhub.entity.configuration.Printer;
|
||||
import com.printhub.printhub.repository.configuration.PrinterRepository;
|
||||
import com.printhub.printhub.specification.configuration.PrinterSpecification;
|
||||
import com.printhub.printhub.utils.datatables.DataTableBuilder;
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
package com.printhub.printhub.service.configuration;
|
||||
|
||||
import com.printhub.printhub.entity.configuration.User;
|
||||
import com.printhub.printhub.repository.configuration.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.*;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class UserService implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
public void register(String username, String password, String role) {
|
||||
User user = new User();
|
||||
user.setUsername(username);
|
||||
user.setPassword(passwordEncoder.encode(password));
|
||||
user.setRole("ROLE_" + role.toUpperCase());
|
||||
user.setEnabled(true);
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||
if (userOpt.isEmpty() || userOpt.get().getDeletedAt() != null) {
|
||||
throw new UsernameNotFoundException("Usuario no encontrado o eliminado");
|
||||
}
|
||||
|
||||
User user = userOpt.get();
|
||||
return new org.springframework.security.core.userdetails.User(
|
||||
user.getUsername(),
|
||||
user.getPassword(),
|
||||
user.isEnabled(),
|
||||
true,
|
||||
true,
|
||||
user.getDeletedAt() == null,
|
||||
Collections.singletonList(new SimpleGrantedAuthority(user.getRole()))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package com.printhub.printhub.specification.configuration;
|
||||
|
||||
import com.printhub.printhub.model.configuration.Printer;
|
||||
import com.printhub.printhub.entity.configuration.Printer;
|
||||
import com.printhub.printhub.utils.datatables.DataTableRequest;
|
||||
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
@ -1 +1,10 @@
|
||||
# Breadcrumbs
|
||||
t-paginas=Pages
|
||||
|
||||
# Roles
|
||||
t-role.ROLE_ADMIN=Administrator
|
||||
t-role.ROLE_USER=User
|
||||
|
||||
# Topbar
|
||||
t-topbar-logout=Logout
|
||||
t-topbar-profile=Profile
|
||||
16
src/main/resources/i18n/en/login.properties
Normal file
16
src/main/resources/i18n/en/login.properties
Normal file
@ -0,0 +1,16 @@
|
||||
t-login-title=Welcome to Print Hub!
|
||||
t-login-subtitle=Sign in to continue
|
||||
t-login-username=Username
|
||||
t-login-password=Password
|
||||
t-login-button=Sign In
|
||||
|
||||
t-login-password-recovery-message=Forgot your password?
|
||||
t-login-password-recovery=Recover Password
|
||||
|
||||
t-login-signup-message=Don't have an account ?
|
||||
t-login-signup-signup=Sign Up
|
||||
|
||||
t-login-logout=You have logged out successfully
|
||||
t-login-error=Incorrect username or password
|
||||
|
||||
|
||||
@ -1 +1,10 @@
|
||||
# Breadcrumbs
|
||||
t-paginas=Páginas
|
||||
|
||||
# Roles
|
||||
t-role.ROLE_ADMIN=Administrador
|
||||
t-role.ROLE_USER=Usuario
|
||||
|
||||
# Topbar
|
||||
t-topbar-logout=Cerrar sesión
|
||||
t-topbar-profile=Perfil
|
||||
14
src/main/resources/i18n/es/login.properties
Normal file
14
src/main/resources/i18n/es/login.properties
Normal file
@ -0,0 +1,14 @@
|
||||
t-login-title=¡Bienvenido a Print Hub!
|
||||
t-login-subtitle=Inicia sesión para continuar
|
||||
t-login-username=Nombre de usuario
|
||||
t-login-password=Contraseña
|
||||
t-login-button=Iniciar sesión
|
||||
|
||||
t-login-password-recovery-message=¿Olvidaste tu contraseña?
|
||||
t-login-password-recovery=Recuperar contraseña
|
||||
|
||||
t-login-signup-message=¿No tienes una cuenta?
|
||||
t-login-signup-signup=Registrarse
|
||||
|
||||
t-login-logout=Has cerrado sesión correctamente
|
||||
t-login-error=Nombre de usuario o contraseña incorrectos
|
||||
@ -49,6 +49,7 @@
|
||||
<th:block layout:fragment="pagejs" />
|
||||
<!-- App js -->
|
||||
<script th:src="@{/assets/js/app.js}"></script>
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
168
src/main/resources/templates/printhub/login.html
Normal file
168
src/main/resources/templates/printhub/login.html
Normal file
@ -0,0 +1,168 @@
|
||||
<!doctype html>
|
||||
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" lang="en"
|
||||
data-layout="vertical" data-topbar="light" data-sidebar="dark" data-sidebar-size="lg" data-sidebar-image="none"
|
||||
data-preloader="disable">
|
||||
|
||||
<head>
|
||||
<!--page title-->
|
||||
<div th:replace="~{printhub/partials/title-meta :: title-meta('Sign In')}"></div>
|
||||
|
||||
<!-- Page CSS -->
|
||||
<div th:replace="~{printhub/partials/head-css :: head-css}"></div>
|
||||
|
||||
<style>
|
||||
.auth-page-content {
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
.auth-page-content.lang-loaded {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="auth-page-wrapper auth-bg-cover py-5 d-flex justify-content-center align-items-center min-vh-100">
|
||||
<div class="bg-overlay"></div>
|
||||
<!-- auth-page content -->
|
||||
<div class="auth-page-content overflow-hidden pt-lg-5 lang-loading">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 mx-auto">
|
||||
<div class="card overflow-hidden">
|
||||
<div class="row g-0">
|
||||
<div class="p-lg-5 p-4">
|
||||
<div class="text-end mb-3">
|
||||
<button class="btn btn-sm me-2" onclick="setLang('es')">
|
||||
<img src="/assets/images/flags/es.svg" alt="ES" height="16">
|
||||
</button>
|
||||
<button class="btn btn-sm" onclick="setLang('en')">
|
||||
<img src="/assets/images/flags/gb.svg" alt="EN" height="16">
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<h5 class="text-primary" data-key="t-login-title">Welcome Back !</h5>
|
||||
<p class="text-muted" data-key="t-login-subtitle">Sign in to continue to
|
||||
Velzon.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<div th:if="${param.error}" class="alert alert-danger text-center"
|
||||
data-key="t-login-error">
|
||||
Usuario o contraseña incorrectos.
|
||||
</div>
|
||||
<div th:if="${param.logout}" class="alert alert-success text-center"
|
||||
data-key="t-login-logout">
|
||||
Has cerrado sesión correctamente.
|
||||
</div>
|
||||
<form method="post" th:action="@{/login}">
|
||||
<div class="mb-3">
|
||||
<label for="username" class="form-label"
|
||||
data-key="t-login-username">Username</label>
|
||||
<input type="text" name="username" class="form-control" id="username"
|
||||
placeholder="Enter username" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="password-input"
|
||||
data-key="t-login-password">Password</label>
|
||||
<input type="password" name="password" class="form-control"
|
||||
id="password-input" placeholder="Enter password" required>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-secondary w-100" type="submit"
|
||||
data-key="t-login-button">Sign
|
||||
In</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mt-5 text-center">
|
||||
<p class="mb-0">
|
||||
<span data-key="t-login-signup-message">Don't have an account ?</span>
|
||||
<a href="/signup" class="link">
|
||||
<i class="fa fa-user-plus me-1"></i>
|
||||
<span data-key="t-login-signup-signup" class="label">Signup</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div> <!-- end row -->
|
||||
</div> <!-- end card -->
|
||||
</div> <!-- end col -->
|
||||
|
||||
</div> <!-- end row -->
|
||||
</div> <!-- end container -->
|
||||
</div> <!-- end auth page content -->
|
||||
|
||||
<!-- footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="text-center">
|
||||
<p class="mb-0">©
|
||||
<script>document.write(new Date().getFullYear())</script> PrintHub <i
|
||||
class="mdi mdi-heart text-danger"></i> by JJO & IMN
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- end Footer -->
|
||||
</div>
|
||||
|
||||
<div th:replace="~{theme/partials/vendor-scripts :: scripts}"></div>
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
|
||||
<!-- password-addon init -->
|
||||
<script src="/assets/js/pages/password-addon.init.js"></script>
|
||||
<script>
|
||||
function setLang(lang) {
|
||||
localStorage.setItem("lang", lang);
|
||||
applyTranslations(lang);
|
||||
}
|
||||
|
||||
function applyTranslations(lang) {
|
||||
const container = document.querySelector('.auth-page-content');
|
||||
|
||||
// Asegura que está invisible al inicio
|
||||
container?.classList.remove('lang-loaded');
|
||||
|
||||
fetch('/api/lang?lang=' + lang,{
|
||||
cache: "no-store"
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
document.querySelectorAll('[data-key]').forEach(el => {
|
||||
const key = el.getAttribute('data-key');
|
||||
if (data[key]) {
|
||||
if (el.children.length === 0) {
|
||||
el.textContent = data[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
// Aquí aplica el fade cuando ya están las traducciones
|
||||
requestAnimationFrame(() => {
|
||||
container?.classList.add('lang-loaded');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const lang = localStorage.getItem("lang") || "es";
|
||||
applyTranslations(lang);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -27,7 +27,9 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-sm px-3 fs-16 header-item vertical-menu-btn topnav-hamburger" id="topnav-hamburger-icon">
|
||||
<button type="button"
|
||||
class="btn btn-sm px-3 fs-16 header-item vertical-menu-btn topnav-hamburger"
|
||||
id="topnav-hamburger-icon">
|
||||
<span class="hamburger-icon">
|
||||
<span></span>
|
||||
<span></span>
|
||||
@ -39,20 +41,25 @@
|
||||
<div class="d-flex align-items-center">
|
||||
|
||||
<div class="dropdown ms-1 topbar-head-dropdown header-item">
|
||||
<button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle"
|
||||
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<img id="header-lang-img" src="" alt="Header Language" height="20" class="rounded">
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
|
||||
<!-- item-->
|
||||
<a href="javascript:void(0);" class="dropdown-item notify-item language" data-lang="es" title="Spanish">
|
||||
<img src="/assets/images/flags/spain.svg" alt="user-image" class="me-2 rounded" height="18">
|
||||
<a href="javascript:void(0);" class="dropdown-item notify-item language" data-lang="es"
|
||||
title="Spanish">
|
||||
<img src="/assets/images/flags/spain.svg" alt="user-image" class="me-2 rounded"
|
||||
height="18">
|
||||
<span class="align-middle">Español</span>
|
||||
</a>
|
||||
|
||||
<!-- item-->
|
||||
<a href="javascript:void(0);" class="dropdown-item notify-item language py-2" data-lang="en" title="English">
|
||||
<img src="/assets/images/flags/gb.svg" alt="user-image" class="me-2 rounded" height="18">
|
||||
<a href="javascript:void(0);" class="dropdown-item notify-item language py-2"
|
||||
data-lang="en" title="English">
|
||||
<img src="/assets/images/flags/gb.svg" alt="user-image" class="me-2 rounded"
|
||||
height="18">
|
||||
<span class="align-middle">English</span>
|
||||
</a>
|
||||
|
||||
@ -60,40 +67,58 @@
|
||||
</div>
|
||||
|
||||
<div class="ms-1 header-item d-none d-sm-flex">
|
||||
<button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle" data-toggle="fullscreen">
|
||||
<button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle"
|
||||
data-toggle="fullscreen">
|
||||
<i class='bx bx-fullscreen fs-22'></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="ms-1 header-item d-none d-sm-flex">
|
||||
<button type="button" class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle light-dark-mode">
|
||||
<button type="button"
|
||||
class="btn btn-icon btn-topbar btn-ghost-secondary rounded-circle light-dark-mode">
|
||||
<i class='bx bx-moon fs-22'></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="dropdown ms-sm-3 header-item topbar-user">
|
||||
<button type="button" class="btn" id="page-header-user-dropdown" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<button type="button" class="btn" id="page-header-user-dropdown" data-bs-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
<span class="d-flex align-items-center">
|
||||
<img class="rounded-circle header-profile-user" src="/assets/images/users/avatar-1.jpg" alt="Header Avatar">
|
||||
<span class="text-start ms-xl-2">
|
||||
<span class="d-none d-xl-inline-block ms-1 fw-medium user-name-text">Anna Adame</span>
|
||||
<span class="d-none d-xl-block ms-1 fs-12 text-muted user-name-sub-text">Founder</span>
|
||||
<span class="d-none d-xl-inline-block ms-1 fw-medium user-name-text"
|
||||
th:text="${currentUser.firstName + ' ' + currentUser.lastName}">
|
||||
</span>
|
||||
<span class="d-none d-xl-block ms-1 fs-12 text-muted user-name-sub-text"
|
||||
th:attr="data-key=${'t-role.' + currentUser.role} ?: ${currentUser.role}"
|
||||
th:text="#{${'t-role.' + currentUser.role} ?: ${currentUser.role}}">
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<!-- item-->
|
||||
<h6 class="dropdown-header">Welcome Anna!</h6>
|
||||
<a class="dropdown-item" href="/pages-profile"><i class="mdi mdi-account-circle text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Profile</span></a>
|
||||
<a class="dropdown-item" href="/apps-chat"><i class="mdi mdi-message-text-outline text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Messages</span></a>
|
||||
<a class="dropdown-item" href="/apps-tasks-kanban"><i class="mdi mdi-calendar-check-outline text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Taskboard</span></a>
|
||||
<a class="dropdown-item" href="/pages-faqs"><i class="mdi mdi-lifebuoy text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Help</span></a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="/pages-profile"><i class="mdi mdi-wallet text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Balance : <b>$5971.67</b></span></a>
|
||||
<a class="dropdown-item" href="/pages-profile-settings"><span class="badge bg-soft-success text-success mt-1 float-end">New</span><i class="mdi mdi-cog-outline text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Settings</span></a>
|
||||
<a class="dropdown-item" href="auth-lockscreen-basic"><i class="mdi mdi-lock text-muted fs-16 align-middle me-1"></i> <span class="align-middle">Lock screen</span></a>
|
||||
<a class="dropdown-item" href="auth-logout-basic"><i class="mdi mdi-logout text-muted fs-16 align-middle me-1"></i> <span class="align-middle" data-key="t-logout">Logout</span></a>
|
||||
<h6 class="dropdown-header" th:if="${currentUser != null}">
|
||||
Bienvenido <span
|
||||
th:text="${currentUser.firstName + ' ' + currentUser.lastName}">Usuario</span>!
|
||||
</h6>
|
||||
<h6 class="dropdown-header" th:unless="${currentUser != null}">
|
||||
Bienvenido invitado
|
||||
</h6>
|
||||
<a class="dropdown-item" href="/pages-profile">
|
||||
<i class="mdi mdi-account-circle text-muted fs-16 align-middle me-1"></i>
|
||||
<span class="align-middle" data-key="t-topbar-profile">
|
||||
Profile
|
||||
</span>
|
||||
</a>
|
||||
<a class="dropdown-item" href="#"
|
||||
onclick="document.getElementById('logout-form').submit(); return false;">
|
||||
<i class="mdi mdi-logout text-muted fs-16 align-middle me-1"></i>
|
||||
<span class="align-middle" data-key="t-topbar-logout">Logout</span>
|
||||
</a>
|
||||
|
||||
<form id="logout-form" th:action="@{/logout}" method="post" style="display:none;">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user