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>
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Security -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-j</artifactId>
|
<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.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
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.service.configuration.PrinterService;
|
||||||
import com.printhub.printhub.utils.datatables.DataTableRequest;
|
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.*;
|
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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -1,9 +1,10 @@
|
|||||||
package com.printhub.printhub.repository.configuration;
|
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.JpaRepository;
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||||
|
|
||||||
|
import com.printhub.printhub.entity.configuration.Printer;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface PrinterRepository extends JpaRepository<Printer, Long>, JpaSpecificationExecutor<Printer> {
|
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;
|
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.repository.configuration.PrinterRepository;
|
||||||
import com.printhub.printhub.specification.configuration.PrinterSpecification;
|
import com.printhub.printhub.specification.configuration.PrinterSpecification;
|
||||||
import com.printhub.printhub.utils.datatables.DataTableBuilder;
|
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;
|
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 com.printhub.printhub.utils.datatables.DataTableRequest;
|
||||||
|
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
|||||||
@ -1 +1,10 @@
|
|||||||
t-paginas=Pages
|
# 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 @@
|
|||||||
t-paginas=Páginas
|
# 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" />
|
<th:block layout:fragment="pagejs" />
|
||||||
<!-- App js -->
|
<!-- App js -->
|
||||||
<script th:src="@{/assets/js/app.js}"></script>
|
<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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</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>
|
</a>
|
||||||
</div>
|
</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 class="hamburger-icon">
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
@ -39,20 +41,25 @@
|
|||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
|
|
||||||
<div class="dropdown ms-1 topbar-head-dropdown header-item">
|
<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">
|
<img id="header-lang-img" src="" alt="Header Language" height="20" class="rounded">
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-end">
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
|
|
||||||
<!-- item-->
|
<!-- item-->
|
||||||
<a href="javascript:void(0);" class="dropdown-item notify-item language" data-lang="es" title="Spanish">
|
<a href="javascript:void(0);" class="dropdown-item notify-item language" data-lang="es"
|
||||||
<img src="/assets/images/flags/spain.svg" alt="user-image" class="me-2 rounded" height="18">
|
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>
|
<span class="align-middle">Español</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- item-->
|
<!-- item-->
|
||||||
<a href="javascript:void(0);" class="dropdown-item notify-item language py-2" data-lang="en" title="English">
|
<a href="javascript:void(0);" class="dropdown-item notify-item language py-2"
|
||||||
<img src="/assets/images/flags/gb.svg" alt="user-image" class="me-2 rounded" height="18">
|
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>
|
<span class="align-middle">English</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@ -60,40 +67,58 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ms-1 header-item d-none d-sm-flex">
|
<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>
|
<i class='bx bx-fullscreen fs-22'></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ms-1 header-item d-none d-sm-flex">
|
<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>
|
<i class='bx bx-moon fs-22'></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="dropdown ms-sm-3 header-item topbar-user">
|
<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">
|
<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="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-inline-block ms-1 fw-medium user-name-text"
|
||||||
<span class="d-none d-xl-block ms-1 fs-12 text-muted user-name-sub-text">Founder</span>
|
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>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu dropdown-menu-end">
|
<div class="dropdown-menu dropdown-menu-end">
|
||||||
<!-- item-->
|
<!-- item-->
|
||||||
<h6 class="dropdown-header">Welcome Anna!</h6>
|
<h6 class="dropdown-header" th:if="${currentUser != null}">
|
||||||
<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>
|
Bienvenido <span
|
||||||
<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>
|
th:text="${currentUser.firstName + ' ' + currentUser.lastName}">Usuario</span>!
|
||||||
<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>
|
</h6>
|
||||||
<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>
|
<h6 class="dropdown-header" th:unless="${currentUser != null}">
|
||||||
<div class="dropdown-divider"></div>
|
Bienvenido invitado
|
||||||
<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>
|
</h6>
|
||||||
<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="/pages-profile">
|
||||||
<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>
|
<i class="mdi mdi-account-circle text-muted fs-16 align-middle me-1"></i>
|
||||||
<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>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -101,7 +126,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user