login form okie
This commit is contained in:
+44
@@ -0,0 +1,44 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
|
||||
|
||||
private final OAuth2AuthorizationRequestResolver defaultResolver;
|
||||
|
||||
public CustomOAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository, String authorizationRequestBaseUri) {
|
||||
this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, authorizationRequestBaseUri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
|
||||
OAuth2AuthorizationRequest req = defaultResolver.resolve(request);
|
||||
return customizeAuthorizationRequest(req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) {
|
||||
OAuth2AuthorizationRequest req = defaultResolver.resolve(request, clientRegistrationId);
|
||||
return customizeAuthorizationRequest(req);
|
||||
}
|
||||
|
||||
private OAuth2AuthorizationRequest customizeAuthorizationRequest(OAuth2AuthorizationRequest req) {
|
||||
if (req == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> additionalParameters = new LinkedHashMap<>(req.getAdditionalParameters());
|
||||
additionalParameters.put("prompt", "select_account");
|
||||
|
||||
return OAuth2AuthorizationRequest.from(req)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.sisvietnamvn.web.config;
|
||||
|
||||
import com.sisvietnamvn.web.domain.Authority;
|
||||
import com.sisvietnamvn.web.repository.AuthorityRepository;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
public class RoleSeeder implements CommandLineRunner {
|
||||
|
||||
private final AuthorityRepository authorityRepository;
|
||||
|
||||
public RoleSeeder(AuthorityRepository authorityRepository) {
|
||||
this.authorityRepository = authorityRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
List<String> roles = Arrays.asList(
|
||||
AuthoritiesConstants.EDITOR,
|
||||
AuthoritiesConstants.AUTHOR,
|
||||
AuthoritiesConstants.CONTRIBUTOR,
|
||||
AuthoritiesConstants.SUBSCRIBER,
|
||||
AuthoritiesConstants.PRE_AUTH_2FA
|
||||
);
|
||||
|
||||
for (String roleName : roles) {
|
||||
if (authorityRepository.findById(roleName).isEmpty()) {
|
||||
Authority authority = new Authority();
|
||||
authority.setName(roleName);
|
||||
authorityRepository.save(authority);
|
||||
System.out.println("Seeded role: " + roleName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
-9
@@ -3,6 +3,7 @@ package com.sisvietnamvn.web.config;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
|
||||
import com.sisvietnamvn.web.security.*;
|
||||
import com.sisvietnamvn.web.security.oauth2.CustomOidcUserService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
@@ -19,6 +20,10 @@ import org.springframework.security.web.SecurityFilterChain;
|
||||
import tech.jhipster.config.JHipsterConstants;
|
||||
import tech.jhipster.config.JHipsterProperties;
|
||||
|
||||
import com.sisvietnamvn.web.security.CustomAuthenticationSuccessHandler;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
|
||||
|
||||
@Configuration
|
||||
@EnableMethodSecurity(securedEnabled = true)
|
||||
public class SecurityConfiguration {
|
||||
@@ -26,10 +31,14 @@ public class SecurityConfiguration {
|
||||
private final Environment env;
|
||||
|
||||
private final JHipsterProperties jHipsterProperties;
|
||||
private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
|
||||
private final ClientRegistrationRepository clientRegistrationRepository;
|
||||
|
||||
public SecurityConfiguration(Environment env, JHipsterProperties jHipsterProperties) {
|
||||
public SecurityConfiguration(Environment env, JHipsterProperties jHipsterProperties, CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler, ClientRegistrationRepository clientRegistrationRepository) {
|
||||
this.env = env;
|
||||
this.jHipsterProperties = jHipsterProperties;
|
||||
this.customAuthenticationSuccessHandler = customAuthenticationSuccessHandler;
|
||||
this.clientRegistrationRepository = clientRegistrationRepository;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -38,18 +47,18 @@ public class SecurityConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) {
|
||||
public SecurityFilterChain filterChain(HttpSecurity http, CustomOidcUserService customOidcUserService) {
|
||||
http.cors(withDefaults())
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.csrf(withDefaults())
|
||||
.authorizeHttpRequests(authz ->
|
||||
// prettier-ignore
|
||||
authz
|
||||
.requestMatchers(HttpMethod.GET, "/", "/about", "/flex-finish", "/tin-tuc", "/tin-tuc/**", "/lien-he",
|
||||
"/manage/**", "/css/**", "/images/**", "/js/**", "/UMass*/**", "/Undergraduate*/**",
|
||||
"/manage/login", "/css/**", "/images/**", "/js/**", "/vendor/**", "/fonts/**", "/login-assets/**", "/UMass*/**", "/Undergraduate*/**",
|
||||
"/favicon.ico", "/favicons/**", "/flex-finish/**", "/contact-us/**", "/uploads/**", "/api/manage/snippets/**", "/page/**", "/news/article/**", "/post/**", "/error",
|
||||
"/about-us", "/specialty", "/doctor", "/service", "/health-library", "/news-and-events", "/patient-support", "/medical-expert", "/umcers", "/bidding", "/contact-us")
|
||||
.permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/manage/**", "/api/manage/media/upload").permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/api/manage/media/upload").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||
.requestMatchers(HttpMethod.POST, "/api/authenticate").permitAll()
|
||||
.requestMatchers(HttpMethod.GET, "/api/authenticate").permitAll()
|
||||
@@ -63,11 +72,30 @@ public class SecurityConfiguration {
|
||||
.requestMatchers("/management/health/**").permitAll()
|
||||
.requestMatchers("/management/info").permitAll()
|
||||
.requestMatchers("/management/prometheus").permitAll()
|
||||
.requestMatchers("/2fa/**").hasAuthority(AuthoritiesConstants.PRE_AUTH_2FA)
|
||||
.requestMatchers("/manage/**").hasAnyAuthority(AuthoritiesConstants.ADMIN, AuthoritiesConstants.EDITOR, AuthoritiesConstants.AUTHOR, AuthoritiesConstants.CONTRIBUTOR, AuthoritiesConstants.SUBSCRIBER, AuthoritiesConstants.USER)
|
||||
.requestMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN))
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.exceptionHandling(exceptions -> exceptions
|
||||
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
|
||||
.accessDeniedHandler(new BearerTokenAccessDeniedHandler()))
|
||||
.formLogin(form -> form
|
||||
.loginPage("/manage/login")
|
||||
.successHandler(customAuthenticationSuccessHandler)
|
||||
.permitAll()
|
||||
)
|
||||
.logout(logout -> logout
|
||||
.logoutUrl("/manage/logout")
|
||||
.logoutSuccessUrl("/manage/login?logout")
|
||||
.permitAll()
|
||||
)
|
||||
.oauth2Login(oauth2 -> oauth2
|
||||
.loginPage("/manage/login")
|
||||
.successHandler(customAuthenticationSuccessHandler)
|
||||
.authorizationEndpoint(authorization -> authorization
|
||||
.authorizationRequestResolver(new CustomOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI))
|
||||
)
|
||||
.userInfoEndpoint(userInfo -> userInfo
|
||||
.oidcUserService(customOidcUserService)
|
||||
)
|
||||
)
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
|
||||
.oauth2ResourceServer(oauth2 -> oauth2.jwt(withDefaults()));
|
||||
if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) {
|
||||
http.authorizeHttpRequests(authz -> authz.requestMatchers("/h2-console/**").permitAll())
|
||||
|
||||
@@ -7,10 +7,6 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
public class HomeController {
|
||||
|
||||
|
||||
@GetMapping("/")
|
||||
public String index() {
|
||||
return "index";
|
||||
}
|
||||
|
||||
// Trang Flex Finish (MỚI THÊM)
|
||||
@GetMapping("/flex-finish")
|
||||
|
||||
@@ -46,6 +46,11 @@ public class PageController {
|
||||
return renderPage(pageService.findBySlug(slug), model);
|
||||
}
|
||||
|
||||
@GetMapping("/")
|
||||
public String getHome(Model model) {
|
||||
return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.HOME), model);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/about-us")
|
||||
public String getAboutUs(Model model) { return renderPage(pageService.findByPageType(com.sisvietnamvn.web.domain.PageType.ABOUT_US), model); }
|
||||
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
package com.sisvietnamvn.web.controller;
|
||||
|
||||
import com.sisvietnamvn.web.domain.Authority;
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import com.sisvietnamvn.web.repository.UserRepository;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
import com.sisvietnamvn.web.security.DomainUserDetailsService;
|
||||
import dev.samstevens.totp.code.CodeGenerator;
|
||||
import dev.samstevens.totp.code.CodeVerifier;
|
||||
import dev.samstevens.totp.code.DefaultCodeGenerator;
|
||||
import dev.samstevens.totp.code.DefaultCodeVerifier;
|
||||
import dev.samstevens.totp.exceptions.QrGenerationException;
|
||||
import dev.samstevens.totp.qr.QrData;
|
||||
import dev.samstevens.totp.qr.QrGenerator;
|
||||
import dev.samstevens.totp.qr.ZxingPngQrGenerator;
|
||||
import dev.samstevens.totp.secret.DefaultSecretGenerator;
|
||||
import dev.samstevens.totp.secret.SecretGenerator;
|
||||
import dev.samstevens.totp.time.SystemTimeProvider;
|
||||
import dev.samstevens.totp.time.TimeProvider;
|
||||
import dev.samstevens.totp.util.Utils;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/2fa")
|
||||
public class TwoFactorController {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public TwoFactorController(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
public String show2FaForm(Authentication authentication) {
|
||||
if (authentication == null || !authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals(AuthoritiesConstants.PRE_AUTH_2FA))) {
|
||||
return "redirect:/manage/login";
|
||||
}
|
||||
|
||||
String username = authentication.getName();
|
||||
Optional<User> userOpt = userRepository.findOneByLogin(username);
|
||||
|
||||
if (userOpt.isPresent()) {
|
||||
User user = userOpt.get();
|
||||
if (!user.isUsing2FA()) {
|
||||
return "redirect:/2fa/setup";
|
||||
}
|
||||
}
|
||||
|
||||
return "manage/2fa/verify";
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public String verify2Fa(@RequestParam("code") String code, Authentication authentication, RedirectAttributes redirectAttributes) {
|
||||
String username = authentication.getName();
|
||||
Optional<User> userOpt = userRepository.findOneWithAuthoritiesByLogin(username);
|
||||
|
||||
if (userOpt.isEmpty()) {
|
||||
return "redirect:/manage/login";
|
||||
}
|
||||
|
||||
User user = userOpt.get();
|
||||
if (verifyCode(user.getTotpSecret(), code)) {
|
||||
upgradeSecurityContext(user, authentication);
|
||||
return "redirect:/manage";
|
||||
} else {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "Invalid 2FA code. Please try again.");
|
||||
return "redirect:/2fa";
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/setup")
|
||||
public String showSetupForm(Authentication authentication, Model model) {
|
||||
String username = authentication.getName();
|
||||
Optional<User> userOpt = userRepository.findOneByLogin(username);
|
||||
|
||||
if (userOpt.isEmpty()) {
|
||||
return "redirect:/manage/login";
|
||||
}
|
||||
|
||||
User user = userOpt.get();
|
||||
if (user.isUsing2FA()) {
|
||||
return "redirect:/2fa";
|
||||
}
|
||||
|
||||
if (user.getTotpSecret() == null) {
|
||||
SecretGenerator secretGenerator = new DefaultSecretGenerator();
|
||||
user.setTotpSecret(secretGenerator.generate());
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
try {
|
||||
QrData data = new QrData.Builder()
|
||||
.label(user.getEmail() != null ? user.getEmail() : user.getLogin())
|
||||
.secret(user.getTotpSecret())
|
||||
.issuer("SIS Vietnam")
|
||||
.algorithm(dev.samstevens.totp.code.HashingAlgorithm.SHA1)
|
||||
.digits(6)
|
||||
.period(30)
|
||||
.build();
|
||||
|
||||
QrGenerator generator = new ZxingPngQrGenerator();
|
||||
byte[] imageData = generator.generate(data);
|
||||
String mimeType = generator.getImageMimeType();
|
||||
String dataUri = Utils.getDataUriForImage(imageData, mimeType);
|
||||
|
||||
model.addAttribute("qrCode", dataUri);
|
||||
model.addAttribute("secret", user.getTotpSecret());
|
||||
} catch (QrGenerationException e) {
|
||||
model.addAttribute("errorMessage", "Failed to generate QR Code.");
|
||||
}
|
||||
|
||||
return "manage/2fa/setup";
|
||||
}
|
||||
|
||||
@PostMapping("/setup")
|
||||
public String verifySetup(@RequestParam("code") String code, Authentication authentication, RedirectAttributes redirectAttributes) {
|
||||
String username = authentication.getName();
|
||||
Optional<User> userOpt = userRepository.findOneWithAuthoritiesByLogin(username);
|
||||
|
||||
if (userOpt.isEmpty()) {
|
||||
return "redirect:/manage/login";
|
||||
}
|
||||
|
||||
User user = userOpt.get();
|
||||
if (verifyCode(user.getTotpSecret(), code)) {
|
||||
user.setUsing2FA(true);
|
||||
userRepository.save(user);
|
||||
upgradeSecurityContext(user, authentication);
|
||||
return "redirect:/manage";
|
||||
} else {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "Invalid 2FA code. Please try again.");
|
||||
return "redirect:/2fa/setup";
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifyCode(String secret, String code) {
|
||||
TimeProvider timeProvider = new SystemTimeProvider();
|
||||
CodeGenerator codeGenerator = new DefaultCodeGenerator();
|
||||
CodeVerifier verifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
|
||||
return verifier.isValidCode(secret, code);
|
||||
}
|
||||
|
||||
private void upgradeSecurityContext(User user, Authentication originalAuthentication) {
|
||||
List<GrantedAuthority> authorities = user.getAuthorities().stream()
|
||||
.map(Authority::getName)
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DomainUserDetailsService.UserWithId userWithId = new DomainUserDetailsService.UserWithId(
|
||||
user.getLogin(),
|
||||
user.getPassword(),
|
||||
authorities,
|
||||
user.getId()
|
||||
);
|
||||
|
||||
UsernamePasswordAuthenticationToken newAuth = new UsernamePasswordAuthenticationToken(
|
||||
userWithId, originalAuthentication.getCredentials(), authorities);
|
||||
|
||||
SecurityContextHolder.getContext().setAuthentication(newAuth);
|
||||
}
|
||||
}
|
||||
+3
@@ -12,6 +12,8 @@ import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
|
||||
/**
|
||||
* Controller for managing post Categories in the admin panel.
|
||||
@@ -19,6 +21,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/manage/posts/categories")
|
||||
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\")")
|
||||
public class ManageCategoryController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ManageCategoryController.class);
|
||||
|
||||
+4
-1
@@ -1,8 +1,10 @@
|
||||
package com.sisvietnamvn.web.controller.manage;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
|
||||
/**
|
||||
* Controller for the admin dashboard index page.
|
||||
@@ -10,6 +12,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/manage")
|
||||
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\", \"" + AuthoritiesConstants.AUTHOR + "\", \"" + AuthoritiesConstants.CONTRIBUTOR + "\", \"" + AuthoritiesConstants.SUBSCRIBER + "\", \"" + AuthoritiesConstants.USER + "\")")
|
||||
public class ManageDashboardController {
|
||||
|
||||
@GetMapping({"", "/"})
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package com.sisvietnamvn.web.controller.manage;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
/**
|
||||
* Controller for the admin login page.
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/manage/login")
|
||||
public class ManageLoginController {
|
||||
|
||||
@GetMapping
|
||||
public String login() {
|
||||
return "manage/login";
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -13,13 +13,15 @@ import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
/**
|
||||
* Controller for managing CMS Pages in the admin panel.
|
||||
* Provides CRUD operations for static pages.
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/manage/pages")
|
||||
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\")")
|
||||
public class ManagePageController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ManagePageController.class);
|
||||
@@ -62,6 +64,7 @@ public class ManagePageController {
|
||||
model.addAttribute("page", page);
|
||||
model.addAttribute("statuses", PageStatus.values());
|
||||
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
|
||||
model.addAttribute("layouts", com.sisvietnamvn.web.domain.PageLayout.values());
|
||||
model.addAttribute("isNew", true);
|
||||
return "manage/pages/form";
|
||||
}
|
||||
@@ -78,6 +81,7 @@ public class ManagePageController {
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("statuses", PageStatus.values());
|
||||
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
|
||||
model.addAttribute("layouts", com.sisvietnamvn.web.domain.PageLayout.values());
|
||||
model.addAttribute("isNew", true);
|
||||
return "manage/pages/form";
|
||||
}
|
||||
@@ -100,6 +104,7 @@ public class ManagePageController {
|
||||
model.addAttribute("page", pageOptional.get());
|
||||
model.addAttribute("statuses", PageStatus.values());
|
||||
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
|
||||
model.addAttribute("layouts", com.sisvietnamvn.web.domain.PageLayout.values());
|
||||
model.addAttribute("isNew", false);
|
||||
return "manage/pages/form";
|
||||
}
|
||||
@@ -117,6 +122,7 @@ public class ManagePageController {
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("statuses", PageStatus.values());
|
||||
model.addAttribute("pageTypes", com.sisvietnamvn.web.domain.PageType.values());
|
||||
model.addAttribute("layouts", com.sisvietnamvn.web.domain.PageLayout.values());
|
||||
model.addAttribute("isNew", false);
|
||||
return "manage/pages/form";
|
||||
}
|
||||
|
||||
+3
@@ -18,6 +18,8 @@ import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
|
||||
/**
|
||||
* Controller for managing blog Posts in the admin panel.
|
||||
@@ -25,6 +27,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/manage/posts")
|
||||
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\", \"" + AuthoritiesConstants.AUTHOR + "\", \"" + AuthoritiesConstants.CONTRIBUTOR + "\")")
|
||||
public class ManagePostController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ManagePostController.class);
|
||||
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
package com.sisvietnamvn.web.controller.manage;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import com.sisvietnamvn.web.hook.HookManager;
|
||||
import com.sisvietnamvn.web.service.UserService;
|
||||
import com.sisvietnamvn.web.service.dto.AdminUserDTO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* Controller for managing the logged-in User's Profile in the admin panel.
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/manage/profile")
|
||||
@PreAuthorize("isAuthenticated()")
|
||||
public class ManageProfileController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ManageProfileController.class);
|
||||
|
||||
private final UserService userService;
|
||||
private final HookManager hookManager;
|
||||
|
||||
public ManageProfileController(UserService userService, HookManager hookManager) {
|
||||
this.userService = userService;
|
||||
this.hookManager = hookManager;
|
||||
}
|
||||
|
||||
@ModelAttribute
|
||||
public void adminInit() {
|
||||
hookManager.doAction("admin_init");
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /manage/profile : view the current user's profile form.
|
||||
*/
|
||||
@GetMapping
|
||||
public String showProfileForm(Model model, RedirectAttributes redirectAttributes) {
|
||||
LOG.debug("Request to show profile form");
|
||||
Optional<User> userOptional = userService.getUserWithAuthorities();
|
||||
if (userOptional.isEmpty()) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "User session not found.");
|
||||
return "redirect:/manage";
|
||||
}
|
||||
|
||||
model.addAttribute("userDTO", new AdminUserDTO(userOptional.get()));
|
||||
return "manage/users/profile";
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /manage/profile/update : update basic information for the current user.
|
||||
*/
|
||||
@PostMapping("/update")
|
||||
public String updateProfile(@Valid @ModelAttribute("userDTO") AdminUserDTO userDTO,
|
||||
BindingResult bindingResult,
|
||||
Model model,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
LOG.debug("Request to update current user Profile : {}", userDTO);
|
||||
if (bindingResult.hasErrors()) {
|
||||
return "manage/users/profile";
|
||||
}
|
||||
|
||||
try {
|
||||
userService.updateUser(userDTO.getFirstName(), userDTO.getLastName(), userDTO.getEmail(), userDTO.getLangKey(), userDTO.getImageUrl());
|
||||
redirectAttributes.addFlashAttribute("successMessage", "Profile updated successfully!");
|
||||
} catch (Exception e) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "Error updating profile: " + e.getMessage());
|
||||
}
|
||||
|
||||
return "redirect:/manage/profile";
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /manage/profile/password : change the current user's password.
|
||||
*/
|
||||
@PostMapping("/password")
|
||||
public String changePassword(@RequestParam("currentPassword") String currentPassword,
|
||||
@RequestParam("newPassword") String newPassword,
|
||||
@RequestParam("confirmPassword") String confirmPassword,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
LOG.debug("Request to change password for current user");
|
||||
|
||||
if (!newPassword.equals(confirmPassword)) {
|
||||
redirectAttributes.addFlashAttribute("errorPasswordMessage", "New password and confirm password do not match!");
|
||||
return "redirect:/manage/profile";
|
||||
}
|
||||
|
||||
try {
|
||||
userService.changePassword(currentPassword, newPassword);
|
||||
redirectAttributes.addFlashAttribute("successPasswordMessage", "Password changed successfully!");
|
||||
} catch (Exception e) {
|
||||
redirectAttributes.addFlashAttribute("errorPasswordMessage", "Incorrect current password or error changing password.");
|
||||
}
|
||||
|
||||
return "redirect:/manage/profile";
|
||||
}
|
||||
}
|
||||
+3
@@ -12,6 +12,8 @@ import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
|
||||
/**
|
||||
* Controller for managing post Tags in the admin panel.
|
||||
@@ -19,6 +21,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/manage/posts/tags")
|
||||
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\", \"" + AuthoritiesConstants.AUTHOR + "\")")
|
||||
public class ManageTagController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ManageTagController.class);
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
package com.sisvietnamvn.web.controller.manage;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.web.PageableDefault;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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.RequestMapping;
|
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
|
||||
|
||||
import com.sisvietnamvn.web.config.Constants;
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import com.sisvietnamvn.web.hook.HookManager;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
import com.sisvietnamvn.web.service.UserService;
|
||||
import com.sisvietnamvn.web.service.dto.AdminUserDTO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
|
||||
/**
|
||||
* Controller for managing Users in the admin panel.
|
||||
*/
|
||||
@Controller
|
||||
@RequestMapping("/manage/users")
|
||||
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||
public class ManageUserController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ManageUserController.class);
|
||||
|
||||
private final UserService userService;
|
||||
private final HookManager hookManager;
|
||||
|
||||
public ManageUserController(UserService userService, HookManager hookManager) {
|
||||
this.userService = userService;
|
||||
this.hookManager = hookManager;
|
||||
}
|
||||
|
||||
@ModelAttribute
|
||||
public void adminInit() {
|
||||
hookManager.doAction("admin_init");
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /manage/users : get all users.
|
||||
*/
|
||||
@GetMapping
|
||||
public String listUsers(@PageableDefault(size = 20, sort = "login") Pageable pageable, Model model) {
|
||||
LOG.debug("REST request to get all User for an admin");
|
||||
Page<AdminUserDTO> page = userService.getAllManagedUsers(pageable);
|
||||
model.addAttribute("users", page.getContent());
|
||||
model.addAttribute("page", page);
|
||||
return "manage/users/list";
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /manage/users/new : show create form.
|
||||
*/
|
||||
@GetMapping("/new")
|
||||
public String showCreateForm(Model model) {
|
||||
LOG.debug("Request to show create user form");
|
||||
AdminUserDTO userDTO = new AdminUserDTO();
|
||||
userDTO.setLangKey(Constants.DEFAULT_LANGUAGE);
|
||||
model.addAttribute("userDTO", userDTO);
|
||||
model.addAttribute("authorities", userService.getAuthorities());
|
||||
model.addAttribute("isNew", true);
|
||||
return "manage/users/form";
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /manage/users : create a new user.
|
||||
*/
|
||||
@PostMapping
|
||||
public String createUser(@Valid @ModelAttribute("userDTO") AdminUserDTO userDTO,
|
||||
BindingResult bindingResult,
|
||||
Model model,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
LOG.debug("Request to create User : {}", userDTO);
|
||||
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("authorities", userService.getAuthorities());
|
||||
model.addAttribute("isNew", true);
|
||||
return "manage/users/form";
|
||||
}
|
||||
|
||||
if (userDTO.getId() != null) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "A new user cannot already have an ID");
|
||||
return "redirect:/manage/users/new";
|
||||
}
|
||||
|
||||
// Ensure email login constraint (optional depending on jhipster config, but good practice)
|
||||
if (userDTO.getLogin() != null) {
|
||||
userDTO.setLogin(userDTO.getLogin().toLowerCase());
|
||||
}
|
||||
|
||||
try {
|
||||
User newUser = userService.createUser(userDTO);
|
||||
redirectAttributes.addFlashAttribute("successMessage", "User created successfully! Login: " + newUser.getLogin());
|
||||
} catch (Exception e) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "Error creating user: " + e.getMessage());
|
||||
return "redirect:/manage/users/new";
|
||||
}
|
||||
|
||||
return "redirect:/manage/users";
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /manage/users/{login}/edit : show edit form.
|
||||
*/
|
||||
@GetMapping("/{login}/edit")
|
||||
public String showEditForm(@PathVariable String login, Model model, RedirectAttributes redirectAttributes) {
|
||||
LOG.debug("Request to show edit form for User : {}", login);
|
||||
Optional<User> userOptional = userService.getUserWithAuthoritiesByLogin(login);
|
||||
if (userOptional.isEmpty()) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "User not found.");
|
||||
return "redirect:/manage/users";
|
||||
}
|
||||
|
||||
model.addAttribute("userDTO", new AdminUserDTO(userOptional.get()));
|
||||
model.addAttribute("authorities", userService.getAuthorities());
|
||||
model.addAttribute("isNew", false);
|
||||
return "manage/users/form";
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /manage/users/{login} : update a user.
|
||||
*/
|
||||
@PostMapping("/{login}")
|
||||
public String updateUser(@PathVariable String login,
|
||||
@Valid @ModelAttribute("userDTO") AdminUserDTO userDTO,
|
||||
BindingResult bindingResult,
|
||||
Model model,
|
||||
RedirectAttributes redirectAttributes) {
|
||||
LOG.debug("Request to update User : {}", userDTO);
|
||||
if (bindingResult.hasErrors()) {
|
||||
model.addAttribute("authorities", userService.getAuthorities());
|
||||
model.addAttribute("isNew", false);
|
||||
return "manage/users/form";
|
||||
}
|
||||
|
||||
Optional<User> existingUser = userService.getUserWithAuthoritiesByLogin(login);
|
||||
if (existingUser.isEmpty()) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "User not found.");
|
||||
return "redirect:/manage/users";
|
||||
}
|
||||
|
||||
userDTO.setId(existingUser.get().getId());
|
||||
|
||||
try {
|
||||
userService.updateUser(userDTO);
|
||||
redirectAttributes.addFlashAttribute("successMessage", "User updated successfully!");
|
||||
} catch (Exception e) {
|
||||
redirectAttributes.addFlashAttribute("errorMessage", "Error updating user: " + e.getMessage());
|
||||
return "redirect:/manage/users/" + login + "/edit";
|
||||
}
|
||||
|
||||
return "redirect:/manage/users";
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /manage/users/{login}/delete : delete a user.
|
||||
*/
|
||||
@PostMapping("/{login}/delete")
|
||||
public String deleteUser(@PathVariable String login, RedirectAttributes redirectAttributes) {
|
||||
LOG.debug("Request to delete User : {}", login);
|
||||
userService.deleteUser(login);
|
||||
redirectAttributes.addFlashAttribute("successMessage", "User deleted successfully!");
|
||||
return "redirect:/manage/users";
|
||||
}
|
||||
}
|
||||
+3
@@ -15,9 +15,12 @@ import java.nio.file.StandardCopyOption;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/manage/media")
|
||||
@PreAuthorize("hasAnyAuthority(\"" + AuthoritiesConstants.ADMIN + "\", \"" + AuthoritiesConstants.EDITOR + "\", \"" + AuthoritiesConstants.AUTHOR + "\", \"" + AuthoritiesConstants.CONTRIBUTOR + "\")")
|
||||
public class MediaController {
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(MediaController.class);
|
||||
|
||||
+3
@@ -10,12 +10,15 @@ import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import com.sisvietnamvn.web.security.AuthoritiesConstants;
|
||||
|
||||
/**
|
||||
* REST controller for fetching predefined HTML snippets to be previewed in Editor.js.
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/manage/snippets")
|
||||
@PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
|
||||
public class SnippetController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SnippetController.class);
|
||||
|
||||
@@ -53,6 +53,11 @@ public class Page extends AbstractAuditingEntity<Long> {
|
||||
@Column(name = "display_order")
|
||||
private Integer displayOrder = 0;
|
||||
|
||||
@NotNull
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "layout", length = 20, nullable = false)
|
||||
private PageLayout layout = PageLayout.STANDARD;
|
||||
|
||||
// --- Getters and Setters ---
|
||||
|
||||
@Override
|
||||
@@ -120,6 +125,14 @@ public class Page extends AbstractAuditingEntity<Long> {
|
||||
this.displayOrder = displayOrder;
|
||||
}
|
||||
|
||||
public PageLayout getLayout() {
|
||||
return layout;
|
||||
}
|
||||
|
||||
public void setLayout(PageLayout layout) {
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
// --- equals, hashCode, toString ---
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.sisvietnamvn.web.domain;
|
||||
|
||||
/**
|
||||
* Enumeration for Page Layout options.
|
||||
*/
|
||||
public enum PageLayout {
|
||||
STANDARD,
|
||||
SIDEBAR,
|
||||
FULL_WIDTH
|
||||
}
|
||||
@@ -85,6 +85,12 @@ public class User extends AbstractAuditingEntity<Long> implements Serializable {
|
||||
@Column(name = "reset_date")
|
||||
private Instant resetDate = null;
|
||||
|
||||
@Column(name = "using_2fa", nullable = false)
|
||||
private boolean using2FA = false;
|
||||
|
||||
@Column(name = "totp_secret")
|
||||
private String totpSecret;
|
||||
|
||||
@JsonIgnore
|
||||
@ManyToMany
|
||||
@JoinTable(
|
||||
@@ -185,6 +191,22 @@ public class User extends AbstractAuditingEntity<Long> implements Serializable {
|
||||
this.resetDate = resetDate;
|
||||
}
|
||||
|
||||
public boolean isUsing2FA() {
|
||||
return using2FA;
|
||||
}
|
||||
|
||||
public void setUsing2FA(boolean using2FA) {
|
||||
this.using2FA = using2FA;
|
||||
}
|
||||
|
||||
public String getTotpSecret() {
|
||||
return totpSecret;
|
||||
}
|
||||
|
||||
public void setTotpSecret(String totpSecret) {
|
||||
this.totpSecret = totpSecret;
|
||||
}
|
||||
|
||||
public String getLangKey() {
|
||||
return langKey;
|
||||
}
|
||||
|
||||
+10
@@ -7,9 +7,19 @@ public final class AuthoritiesConstants {
|
||||
|
||||
public static final String ADMIN = "ROLE_ADMIN";
|
||||
|
||||
public static final String EDITOR = "ROLE_EDITOR";
|
||||
|
||||
public static final String AUTHOR = "ROLE_AUTHOR";
|
||||
|
||||
public static final String CONTRIBUTOR = "ROLE_CONTRIBUTOR";
|
||||
|
||||
public static final String SUBSCRIBER = "ROLE_SUBSCRIBER";
|
||||
|
||||
public static final String USER = "ROLE_USER";
|
||||
|
||||
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
|
||||
|
||||
public static final String PRE_AUTH_2FA = "ROLE_PRE_AUTH_2FA";
|
||||
|
||||
private AuthoritiesConstants() {}
|
||||
}
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.sisvietnamvn.web.security;
|
||||
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
|
||||
@Component
|
||||
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
|
||||
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
|
||||
|
||||
boolean isPreAuth = authorities.stream()
|
||||
.anyMatch(grantedAuthority -> grantedAuthority.getAuthority().equals(AuthoritiesConstants.PRE_AUTH_2FA));
|
||||
|
||||
if (isPreAuth) {
|
||||
redirectStrategy.sendRedirect(request, response, "/2fa");
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isWordPressRole = authorities.stream()
|
||||
.anyMatch(grantedAuthority -> {
|
||||
String role = grantedAuthority.getAuthority();
|
||||
return role.equals(AuthoritiesConstants.ADMIN) ||
|
||||
role.equals(AuthoritiesConstants.EDITOR) ||
|
||||
role.equals(AuthoritiesConstants.AUTHOR) ||
|
||||
role.equals(AuthoritiesConstants.CONTRIBUTOR) ||
|
||||
role.equals(AuthoritiesConstants.SUBSCRIBER) ||
|
||||
role.equals(AuthoritiesConstants.USER);
|
||||
});
|
||||
|
||||
if (isWordPressRole) {
|
||||
redirectStrategy.sendRedirect(request, response, "/manage");
|
||||
} else {
|
||||
redirectStrategy.sendRedirect(request, response, "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -79,10 +79,12 @@ public class DomainUserDetailsService implements UserDetailsService {
|
||||
}
|
||||
|
||||
public static UserWithId fromUser(User user) {
|
||||
List<GrantedAuthority> grantedAuthorities = Collections.singletonList(new SimpleGrantedAuthority(AuthoritiesConstants.PRE_AUTH_2FA));
|
||||
|
||||
return new UserWithId(
|
||||
user.getLogin(),
|
||||
user.getPassword(),
|
||||
user.getAuthorities().stream().map(Authority::getName).map(SimpleGrantedAuthority::new).toList(),
|
||||
grantedAuthorities,
|
||||
user.getId()
|
||||
);
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.sisvietnamvn.web.security.oauth2;
|
||||
|
||||
import com.sisvietnamvn.web.domain.Authority;
|
||||
import com.sisvietnamvn.web.domain.User;
|
||||
import com.sisvietnamvn.web.repository.UserRepository;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class CustomOidcUserService extends OidcUserService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public CustomOidcUserService(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
OidcUser oidcUser = super.loadUser(userRequest);
|
||||
String email = oidcUser.getEmail();
|
||||
|
||||
System.out.println("===============================");
|
||||
System.out.println("CustomOidcUserService is called!");
|
||||
System.out.println("Google Email: " + email);
|
||||
System.out.println("===============================");
|
||||
|
||||
if (email == null) {
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error("invalid_token", "Email not found from OAuth2 provider", ""));
|
||||
}
|
||||
|
||||
// Map the Google email to an existing User in the database (Option A)
|
||||
Optional<User> userOptional = userRepository.findOneWithAuthoritiesByEmailIgnoreCase(email);
|
||||
|
||||
if (userOptional.isEmpty()) {
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error("user_not_found", "User not found in system. Please contact administrator.", ""));
|
||||
}
|
||||
|
||||
User user = userOptional.get();
|
||||
|
||||
if (!user.isActivated()) {
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error("user_not_activated", "User is not activated.", ""));
|
||||
}
|
||||
|
||||
List<GrantedAuthority> authorities = user.getAuthorities().stream()
|
||||
.map(Authority::getName)
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return new DefaultOidcUser(authorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
|
||||
}
|
||||
}
|
||||
@@ -152,6 +152,16 @@ spring:
|
||||
enabled: true
|
||||
security:
|
||||
oauth2:
|
||||
client:
|
||||
registration:
|
||||
google:
|
||||
client-id: 416910503373-de8dpds39j5klabamn7e9lqcku29okeh.apps.googleusercontent.com
|
||||
client-secret: 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
|
||||
authorization-uri: https://accounts.google.com/o/oauth2/v2/auth?prompt=select_account
|
||||
scope:
|
||||
- openid
|
||||
- email
|
||||
- profile
|
||||
resourceserver:
|
||||
jwt:
|
||||
authority-prefix: ''
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
|
||||
|
||||
<changeSet id="20260629084709-1" author="antigravity">
|
||||
<addColumn tableName="sis_page">
|
||||
<column name="layout" type="varchar(20)" defaultValue="STANDARD">
|
||||
<constraints nullable="false" />
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
|
||||
|
||||
<!--
|
||||
Added WordPress-like roles to jhi_authority.
|
||||
-->
|
||||
<changeSet id="20260629163500-1" author="jhipster">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM jhi_authority WHERE name = 'ROLE_EDITOR'</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="jhi_authority">
|
||||
<column name="name" value="ROLE_EDITOR"/>
|
||||
</insert>
|
||||
<insert tableName="jhi_authority">
|
||||
<column name="name" value="ROLE_AUTHOR"/>
|
||||
</insert>
|
||||
<insert tableName="jhi_authority">
|
||||
<column name="name" value="ROLE_CONTRIBUTOR"/>
|
||||
</insert>
|
||||
<insert tableName="jhi_authority">
|
||||
<column name="name" value="ROLE_SUBSCRIBER"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<databaseChangeLog
|
||||
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
|
||||
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
|
||||
|
||||
<changeSet id="20260629165300-1" author="jhipster">
|
||||
<addColumn tableName="jhi_user">
|
||||
<column name="using_2fa" type="boolean" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="totp_secret" type="varchar(255)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
@@ -1,3 +1,8 @@
|
||||
name
|
||||
ROLE_ADMIN
|
||||
ROLE_USER
|
||||
ROLE_EDITOR
|
||||
ROLE_AUTHOR
|
||||
ROLE_CONTRIBUTOR
|
||||
ROLE_SUBSCRIBER
|
||||
ROLE_PRE_AUTH_2FA
|
||||
|
||||
|
@@ -23,6 +23,9 @@
|
||||
<include file="config/liquibase/changelog/20260625120000_add_page_type.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260628100000_add_post_category_tag.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260628151700_add_post_layout.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260629084709_add_page_layout.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260629163500_add_wordpress_roles.xml" relativeToChangelogFile="false"/>
|
||||
<include file="config/liquibase/changelog/20260629165300_add_totp_to_user.xml" relativeToChangelogFile="false"/>
|
||||
<!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here -->
|
||||
<!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here -->
|
||||
<!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
|
||||
|
||||
+4
File diff suppressed because one or more lines are too long
@@ -0,0 +1,300 @@
|
||||
function ShowMessage(message) {
|
||||
if (message != null) {
|
||||
var jsMessage = $('#jsquickmessage');
|
||||
var toInject = "<div class=\"Metronic-alerts alert alert-success fade in\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-hidden=\"true\"></button>" + message + "</div>";
|
||||
jsMessage.html(toInject);
|
||||
jsMessage.show();
|
||||
$('div.alert').delay(2200).fadeOut();
|
||||
}
|
||||
}
|
||||
function ShowSuccessNotification() {
|
||||
$('span.ajaxsuccessshow').fadeIn().delay('800').fadeOut();
|
||||
}
|
||||
var showProgress = true;
|
||||
//$(document).ajaxStart(function () {
|
||||
// if (showProgress) {
|
||||
// $("#AjaxProgress").show();
|
||||
// }
|
||||
//});
|
||||
|
||||
//$(document).ajaxStop(function () {
|
||||
// $("#AjaxProgress").hide();
|
||||
// showProgress = true;
|
||||
//});
|
||||
|
||||
$(function () {
|
||||
$('div.alert').delay(5000).fadeOut();
|
||||
ChangeLanguage();
|
||||
var app_base = '/';
|
||||
//Language manage Start
|
||||
// Find the encompassing element
|
||||
function getParentElement(element, elementType) {
|
||||
var target = element.parent();
|
||||
var found = false;
|
||||
|
||||
while (target != undefined && !found) {
|
||||
var tagName = target.get(0).tagName;
|
||||
|
||||
if (tagName == undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (tagName.toLowerCase() != elementType) {
|
||||
target = target.parent();
|
||||
} else {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
function tableContext(element) {
|
||||
|
||||
this.Cell = getParentElement(element, "td");
|
||||
this.Row = getParentElement(element, "tr");
|
||||
}
|
||||
|
||||
// Handler for click on the resource edit button
|
||||
$('span.editresource').click(function (e) {
|
||||
e.preventDefault();
|
||||
var tableInfo = new tableContext($(this));
|
||||
|
||||
tableInfo.Cell.find(".saveresource").show();
|
||||
tableInfo.Row.find(".resourcevalueedit").show();
|
||||
tableInfo.Row.find(".resourcevaluedisplay").hide();
|
||||
tableInfo.Cell.find(".editresource").hide();
|
||||
});
|
||||
|
||||
// Handler for click on the resource save button
|
||||
$('span.saveresource').click(function (e) {
|
||||
e.preventDefault();
|
||||
var tableInfo = new tableContext($(this));
|
||||
|
||||
var inputfield = tableInfo.Row.find(".resourcevalueedit textarea");
|
||||
var displayfield = tableInfo.Row.find(".resourcevaluedisplay");
|
||||
|
||||
// Ajax call setup
|
||||
var languageid = inputfield.data('languageid');
|
||||
var resourcekey = inputfield.data('resourcekey');
|
||||
var oldvalue = inputfield.data('oldvalue');
|
||||
var newvalue = inputfield.val();
|
||||
|
||||
// Don't allow a null/empty string value, and don't update if nothing changed
|
||||
if ((newvalue != null && newvalue != "") && (newvalue != oldvalue)) {
|
||||
// Make a view model instance
|
||||
var ajaxEditLanguageValueViewModel = new Object();
|
||||
ajaxEditLanguageValueViewModel.LanguageId = languageid;
|
||||
ajaxEditLanguageValueViewModel.ResourceKey = resourcekey;
|
||||
ajaxEditLanguageValueViewModel.NewValue = newvalue;
|
||||
|
||||
// Ajax call to post the view model to the controller
|
||||
var strung = JSON.stringify(ajaxEditLanguageValueViewModel);
|
||||
|
||||
$.ajax({
|
||||
url: app_base + 'ManageLanguage/UpdateResourceValue',
|
||||
type: 'POST',
|
||||
data: strung,
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
error: function (xhr, ajaxOptions, thrownError) {
|
||||
ShowUserMessage("Error: " + xhr.status + " " + thrownError);
|
||||
}
|
||||
});
|
||||
|
||||
displayfield.text(newvalue);
|
||||
}
|
||||
|
||||
tableInfo.Cell.find(".editresource").show();
|
||||
tableInfo.Row.find(".resourcevaluedisplay").show();
|
||||
tableInfo.Cell.find(".saveresource").hide();
|
||||
tableInfo.Row.find(".resourcevalueedit").hide();
|
||||
});
|
||||
|
||||
|
||||
// Handler for click on the resource key edit button
|
||||
$('span.editresourcekey').click(function (e) {
|
||||
e.preventDefault();
|
||||
var tableInfo = new tableContext($(this));
|
||||
|
||||
tableInfo.Cell.find(".saveresourcekey").show();
|
||||
tableInfo.Row.find(".resourcekeyvalueedit").show();
|
||||
tableInfo.Row.find(".resourcekeyvaluedisplay").hide();
|
||||
tableInfo.Cell.find(".editresourcekey").hide();
|
||||
});
|
||||
|
||||
// Handler for click on the resource key save button
|
||||
$('span.saveresourcekey').click(function (e) {
|
||||
e.preventDefault();
|
||||
var tableInfo = new tableContext($(this));
|
||||
|
||||
var inputfield = tableInfo.Row.find(".resourcekeyvalueedit input");
|
||||
var displayfield = tableInfo.Row.find(".resourcekeyvaluedisplay");
|
||||
|
||||
// Ajax call setup
|
||||
var resourcekeyid = inputfield.data('resourcekeyid');
|
||||
var oldvalue = inputfield.data('oldvalue');
|
||||
var newvalue = inputfield.val();
|
||||
|
||||
// Don't allow a null/empty string value, and don't update if nothing changed
|
||||
if ((newvalue != null && newvalue != "") && (newvalue != oldvalue)) {
|
||||
// Make a view model instance
|
||||
var ajaxEditLanguageKeyViewModel = new Object();
|
||||
ajaxEditLanguageKeyViewModel.ResourceKeyId = resourcekeyid;
|
||||
ajaxEditLanguageKeyViewModel.NewName = newvalue;
|
||||
|
||||
// Ajax call to post the view model to the controller
|
||||
var strung = JSON.stringify(ajaxEditLanguageKeyViewModel);
|
||||
|
||||
$.ajax({
|
||||
url: app_base + 'ManageLanguage/UpdateResourceKey',
|
||||
type: 'POST',
|
||||
data: strung,
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
error: function (xhr, ajaxOptions, thrownError) {
|
||||
ShowUserMessage("Error: " + xhr.status + " " + thrownError);
|
||||
}
|
||||
});
|
||||
|
||||
displayfield.text(newvalue);
|
||||
}
|
||||
|
||||
tableInfo.Cell.find(".editresourcekey").show();
|
||||
tableInfo.Row.find(".resourcekeyvaluedisplay").show();
|
||||
tableInfo.Cell.find(".saveresourcekey").hide();
|
||||
tableInfo.Row.find(".resourcekeyvalueedit").hide();
|
||||
});
|
||||
$('.btn-file :file').on('fileselect', function (event, numFiles, label) {
|
||||
|
||||
var input = $(this).parents('.input-group').find(':text'),
|
||||
log = numFiles > 1 ? numFiles + ' files selected' : label;
|
||||
|
||||
if (input.length) {
|
||||
input.val(log);
|
||||
} else {
|
||||
if (log) alert(log);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
$(document).on('change', '.btn-file :file', function () {
|
||||
var input = $(this),
|
||||
numFiles = input.get(0).files ? input.get(0).files.length : 1,
|
||||
label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
|
||||
input.trigger('fileselect', [numFiles, label]);
|
||||
});
|
||||
|
||||
var isFirstLoad = true;
|
||||
|
||||
function ImportLanguage() {
|
||||
$("#ImportForm").submit();
|
||||
}
|
||||
|
||||
function Import_Complete() {
|
||||
// Check to see if this is the first load of the iFrame
|
||||
if (isFirstLoad == true) {
|
||||
isFirstLoad = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset the import form so the file won't get uploaded again
|
||||
document.getElementById("ImportForm").reset();
|
||||
|
||||
// Grab the content of the textarea we named jsonResult in the hidden iframe
|
||||
var importResults = $.parseJSON($("#UploadTarget").contents().find("#jsonResult")[0].innerHTML);
|
||||
var displayImportResults = "";
|
||||
|
||||
// Redirect as there are no errors
|
||||
if (!importResults.HasErrors && !importResults.HasWarnings) {
|
||||
displayImportResults += "<div>The import was successful.</div>";
|
||||
}
|
||||
|
||||
if (importResults.HasErrors) {
|
||||
displayImportResults += "<div>The import had the following errors:</div>";
|
||||
displayImportResults += formatImportResults(importResults.Errors);
|
||||
}
|
||||
|
||||
if (importResults.HasWarnings) {
|
||||
displayImportResults += "<div>The import had the following warnings:</div>";
|
||||
displayImportResults += formatImportResults(importResults.Warnings);
|
||||
}
|
||||
|
||||
$("#ImportResults").html(displayImportResults);
|
||||
}
|
||||
|
||||
function formatImportResults(jsonErrorsWarnings) {
|
||||
var results = "";
|
||||
var errorsWarnings = jQuery.parseJSON(jsonErrorsWarnings);
|
||||
for (var i = 0; i < errorsWarnings.length; i++) {
|
||||
results += "<div>" + errorsWarnings[i] + "</div>";
|
||||
}
|
||||
return results;
|
||||
}
|
||||
//Language manage End
|
||||
function ChangeLanguage() {
|
||||
var languageSelect = $(".languageselector select");
|
||||
if (languageSelect.length > 0) {
|
||||
languageSelect.change(function () {
|
||||
var langVal = this.value;
|
||||
$.ajax({
|
||||
url: "/Language/ChangeLanguage",
|
||||
type: "POST",
|
||||
cache: false,
|
||||
data: { lang: langVal },
|
||||
success: function (data) {
|
||||
location.reload();
|
||||
},
|
||||
error: function (xhr, ajaxOptions, thrownError) {
|
||||
ShowUserMessage("Error: " + xhr.status + " " + thrownError);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
function decodeHtml(html) {
|
||||
var txt = document.createElement("textarea");
|
||||
txt.innerHTML = html;
|
||||
return txt.value;
|
||||
}
|
||||
|
||||
function AjaxPost(url, data, successFunction, errorFunction) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: url,
|
||||
data: data,
|
||||
success: function (returnData, request) {
|
||||
if (typeof (successFunction) == 'function') {
|
||||
successFunction(returnData, url, data);
|
||||
|
||||
}
|
||||
},
|
||||
error: errorFunction ? errorFunction : DefaultErrorFunction
|
||||
});
|
||||
|
||||
}
|
||||
function AjaxGet(url, data, successFunction, errorFunction) {
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
url: url,
|
||||
data: JSON.stringify(data),
|
||||
success: function (returnData, request) {
|
||||
if (typeof (successFunction) == 'function') {
|
||||
successFunction(returnData, url, data);
|
||||
|
||||
}
|
||||
},
|
||||
error: errorFunction ? errorFunction : DefaultErrorFunction
|
||||
});
|
||||
|
||||
}
|
||||
function DefaultErrorFunction(err) {
|
||||
alert(err.responseText);
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
function ShowMessageControl(control, message) {
|
||||
$(control).notify(message, { position: "bottom", className: "error" });
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 16 MiB |
+22
File diff suppressed because one or more lines are too long
+22
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,360 @@
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* hebrew */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||
}
|
||||
/* math */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||
}
|
||||
/* symbols */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* hebrew */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||
}
|
||||
/* math */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||
}
|
||||
/* symbols */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* hebrew */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||
}
|
||||
/* math */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||
}
|
||||
/* symbols */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
/* cyrillic-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSKmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||
}
|
||||
/* cyrillic */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSumu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||
}
|
||||
/* greek-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSOmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+1F00-1FFF;
|
||||
}
|
||||
/* greek */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSymu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
||||
}
|
||||
/* hebrew */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS2mu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0307-0308, U+0590-05FF, U+200C-2010, U+20AA, U+25CC, U+FB1D-FB4F;
|
||||
}
|
||||
/* math */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTVOmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-208E, U+2090-209C, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
|
||||
}
|
||||
/* symbols */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTUGmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AD, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F7FF, U+1F800-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FA89, U+1FA8F-1FAC6, U+1FACE-1FADC, U+1FADF-1FAE9, U+1FAF0-1FAF8, U+1FB00-1FBFF;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSCmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTSGmu1aB.woff2) format('woff2');
|
||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-stretch: 100%;
|
||||
src: url(https://fonts.gstatic.com/s/opensans/v44/memvYaGs126MiZpBA-UvWbX2vVnXBbObj2OVTS-muw.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
@media (min-width: 1400px){
|
||||
.container, .container-sm, .container-md, .container-lg, .container-xl, .container-xxl {
|
||||
max-width: 1500px!important;
|
||||
}
|
||||
}
|
||||
.dropdown-user.lv-lang-box{
|
||||
display:none;
|
||||
}
|
||||
.page-header.navbar .page-logo .logo-default, .brand-logo img {
|
||||
max-height: 45px !important;
|
||||
margin-top: 1px !important;
|
||||
}
|
||||
|
||||
.top-menu .navbar-nav > li.dropdown-user .dropdown-toggle > img {
|
||||
border-radius: 10px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.lv-notification-box .badge {
|
||||
background-color: #FFD5D8;
|
||||
color: #A85A5C;
|
||||
border: 1px #EFDDDC solid;
|
||||
}
|
||||
|
||||
.top-menu .link-chat, .classic-menu-dropdown.active, .header-menu .menu-nav > .menu-item:first-child {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.active-sub {
|
||||
background-color: #f5f5f9 !important;
|
||||
}
|
||||
|
||||
.active-sub span {
|
||||
color: #e7505a !important;
|
||||
}
|
||||
|
||||
.input-icon-search {
|
||||
display: unset;
|
||||
right: 0px;
|
||||
top: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#kt_aside_menu{
|
||||
padding-bottom: 50px;
|
||||
overflow-y: auto!important;
|
||||
}
|
||||
.gutter-b{
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
.page-content {
|
||||
margin-top: 55px;
|
||||
}
|
||||
@media (min-width: 992px){
|
||||
.header-menu .menu-nav {
|
||||
max-width: 700px;
|
||||
}
|
||||
}
|
||||
.dashboard-stat .details .number{
|
||||
font-size: 20px!important;
|
||||
}
|
||||
:root {
|
||||
/*lacviet text color*/
|
||||
/*lacviet button background, text color*/
|
||||
/*lacviet button background hover, text color hover */
|
||||
/*lacviet button background, text color third*/
|
||||
/*lacviet border left search advance*/
|
||||
/*lacviet bg button light*/
|
||||
--primary-color-lv: #E74C3C;
|
||||
--primary-button-color-lv: #5b5b5b;
|
||||
--second-color-lv: #5b5b5b;
|
||||
--third-color-lv: #C11200;
|
||||
--border-color-lv: #8d8a8a;
|
||||
--light-bg-lv: #E1F0FF;
|
||||
/* --primary-color-lv: #B92A85;
|
||||
--second-color-lv: #891B72;
|
||||
--third-color-lv: #B31E8D;
|
||||
--border-color-lv: #bd82a8;
|
||||
--light-bg-lv: #f2f2f2;*/
|
||||
}
|
||||
.user-login-5 {
|
||||
background: linear-gradient(#fff, #ccc)!important;
|
||||
}
|
||||
.goback a {
|
||||
color: #696969 !important;
|
||||
}
|
||||
.login .mt-checkbox.mt-checkbox-outline>span{
|
||||
border: 1px #696969 solid;
|
||||
}
|
||||
.rem-password .rememberme.font-white,
|
||||
.user-login-5 .login-container > .login-content > h1,
|
||||
.user-login-5 .login-container > .login-content p
|
||||
{
|
||||
color:#696969!important;
|
||||
}
|
||||
.table-scrollable .btn.btn-primary{
|
||||
background-color: #337ab7!important;
|
||||
border-color: #2e6da4!important;
|
||||
}
|
||||
/*module*/
|
||||
.page-header.navbar .page-logo .logo-default, .contract-header #kt_brand img {
|
||||
max-height: 45px !important;
|
||||
height: 45px;
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
/*.have-chat .link-chat, .happy-brithday{
|
||||
display: none;
|
||||
}*/
|
||||
|
||||
.medium .modal-dialog .btn-success {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
#create-document-button.floating-button, .cd-stretchy-nav .stretchy-nav-bg, .login .btn.blue:not(.btn-outline),
|
||||
.login .btn.blue, .login .btn.btn-success, .input-search-single .btn.green:not(.btn-outline)
|
||||
{
|
||||
background-color: var(--primary-color-lv) !important;
|
||||
}
|
||||
.login .btn.blue:not(.btn-outline),
|
||||
.login .btn.blue, .login .btn.btn-success,
|
||||
.user-login-5 .login-container > .login-content > .login-form .form-control,
|
||||
.nav.nav-tabs.nav-tabs-line .nav-link:hover:not(.disabled), .nav.nav-tabs.nav-tabs-line .nav-link.active, .nav.nav-tabs.nav-tabs-line .show > .nav-link
|
||||
{
|
||||
border-color: var(--primary-color-lv) !important;
|
||||
}
|
||||
.page-header.navbar .hor-menu .navbar-nav > li.active > a, .page-header.navbar .hor-menu .navbar-nav > li.active > a, .page-header.navbar .hor-menu .navbar-nav > li.current > a, .page-header.navbar .hor-menu .navbar-nav > li.current > a,
|
||||
.e-btn.e-info, .e-css.e-btn.e-info, .e-footer-content .e-btn.e-info, .e-upload .e-file-select-wrap .e-btn, .btn.blue:not(.btn-outline),
|
||||
.add-menu, .edit-menu,
|
||||
ul#navigation-left-filter-tree > li .title i,
|
||||
#advanced-search-table-header th div,
|
||||
.btn-success,
|
||||
.lv-mainTop .pull-right > a,
|
||||
.lv-mainTop .fa-ellipsis-v,
|
||||
.lv-mainTop .pull-right > a, .lv-mainTop .pull-left > a, #downloadAllFiles,
|
||||
#Schedules thead th {
|
||||
background-color: var(--primary-button-color-lv) !important;
|
||||
}
|
||||
|
||||
.fc-unthemed .fc-toolbar .fc-button.fc-button-active
|
||||
{
|
||||
background-color: var(--primary-button-color-lv) !important;
|
||||
border-color: var(--primary-button-color-lv) !important;
|
||||
}
|
||||
.input-search-single .btn.green:not(.btn-outline){
|
||||
border: 1px var(--primary-color-lv) solid !important;
|
||||
}
|
||||
.input-search-single .btn.green:not(.btn-outline) {
|
||||
border-left: 1px var(--third-color-lv) solid !important;
|
||||
}
|
||||
.e-btn.e-info, .e-css.e-btn.e-info, .e-footer-content .e-btn.e-info, .e-upload .e-file-select-wrap .e-btn, .btn.blue:not(.btn-outline),
|
||||
.add-menu, .edit-menu,
|
||||
ul#navigation-left-filter-tree > li .title i,
|
||||
.btn-success {
|
||||
border-color: var(--primary-button-color-lv) !important;
|
||||
}
|
||||
.page-content-wrapper .e-btn.e-info:hover,
|
||||
.page-content-wrapper .e-css.e-btn.e-info:hover,
|
||||
.page-content-wrapper .e-footer-content .e-btn.e-info:hover,
|
||||
.page-content-wrapper .e-upload .e-file-select-wrap .e-btn:hover .btn.blue:not(.btn-outline):hover,
|
||||
.btn-success:hover,
|
||||
.trinhky .e-btn.e-info:hover, .trinhky .e-css.e-btn.e-info:hover, .trinhky .e-footer-content .e-btn.e-info:hover,
|
||||
.trinhky .e-upload .e-file-select-wrap .e-btn:hover .btn.blue:not(.btn-outline):hover,
|
||||
.btn.green:not(.btn-outline):focus, .btn.green:not(.btn-outline):hover,
|
||||
.fc-unthemed .fc-toolbar .fc-button.fc-button-active:hover {
|
||||
background-color: var(--second-color-lv) !important;
|
||||
border-color: var(--second-color-lv) !important;
|
||||
}
|
||||
|
||||
.page-header.navbar .hor-menu .navbar-nav > li.active > a:hover {
|
||||
background-color: var(--second-color-lv) !important;
|
||||
}
|
||||
|
||||
.page-sidebar .page-sidebar-menu a.nav-toggle + .sub-menu > li > a:hover, .page-sidebar .page-sidebar-menu a.selected,
|
||||
.page-sidebar .page-sidebar-menu .sub-menu > li > a.selected > i,
|
||||
.page-sidebar .page-sidebar-menu a.nav-toggle + .sub-menu > li.selected > a,
|
||||
.dropdown-menu > li > a:hover,
|
||||
.dropdown-menu > li > a.delete-document, .dropdown-menu > li > a.delete-document i,
|
||||
.lv-search #SearchMainDocumentIcon,
|
||||
#MainDocumentDetailContent .document-title,
|
||||
.todo-tasklist-item-text,
|
||||
.btn.btn-light-primary, .title-private-folder,
|
||||
.nav .nav-link:hover:not(.disabled)
|
||||
{
|
||||
color: var(--primary-color-lv) !important;
|
||||
}
|
||||
|
||||
#document-form .lv-btn-se .step-user, #document-form .lv-btn-se .step-user:focus {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.active-sub span, .header .header-menu .menu-nav > .menu-item > .menu-link.active-sub .menu-text, #content-meetings .text-muted,
|
||||
#content-meetings .card-header-meeting .btn.btn-light {
|
||||
color: var(--primary-color-lv) !important;
|
||||
}
|
||||
/*Feature*/
|
||||
.have-chat .link-chat {
|
||||
display: none;
|
||||
}
|
||||
.btn.btn-primary, .lv-res-lang .search-box .input-search-single .btn-search-text-single:hover
|
||||
{
|
||||
background-color: var(--primary-button-color-lv) !important;
|
||||
border-color: var(--primary-button-color-lv) !important;
|
||||
}
|
||||
.scrolltop,
|
||||
#kt_header_menu .menu-link.menu-toggle1,
|
||||
.page-header.navbar .hor-menu .navbar-nav > li.active > a, .page-header.navbar .hor-menu .navbar-nav > li.active > a, .page-header.navbar .hor-menu .navbar-nav > li.current > a, .page-header.navbar .hor-menu .navbar-nav > li.current > a,
|
||||
.lv-res-lang .btn.green:not(.btn-outline), .search-box #searchStr + span > .btn-search,
|
||||
.modal-content .split-container .portlet.light > .portlet-title,
|
||||
.nav.nav-pills .show > .nav-link, .nav.nav-pills .nav-link.active ,
|
||||
.sticky .btn.btn-danger,
|
||||
.btn.btn-primary:hover:not(.btn-text):not(:disabled):not(.disabled),.btn.btn-primary:hover
|
||||
{
|
||||
background-color: var(--primary-color-lv) !important;
|
||||
}
|
||||
.sticky .btn.btn-danger,
|
||||
.btn.btn-primary:hover:not(.btn-text):not(:disabled):not(.disabled), .btn.btn-primary:hover{
|
||||
border-color: var(--primary-color-lv) !important;
|
||||
}
|
||||
.btn.btn-light-primary, .card-header-meeting .btn.btn-light,
|
||||
.card.card-custom.card-more > .card-header .card-title .card-label {
|
||||
background-color: var(--light-bg-lv) !important;
|
||||
}
|
||||
|
||||
.card.card-custom.card-more > .card-header .card-title .card-label:after {
|
||||
border-left-color: var(--light-bg-lv) !important;
|
||||
}
|
||||
|
||||
.page-header.navbar .hor-menu .navbar-nav > li.active > a:hover {
|
||||
background-color: var(--third-color-lv) !important;
|
||||
}
|
||||
.btn.blue:not(.btn-outline).active, .btn.blue:not(.btn-outline):active,
|
||||
.open > .btn.blue:not(.btn-outline).dropdown-toggle,
|
||||
.content-user .btn-info,.btn-info,
|
||||
.doc-group-title a.btn, .doc-group-title button.btn, .content-user .add-role {
|
||||
background-color: var(--primary-button-color-lv) !important;
|
||||
border-color: var(--primary-button-color-lv) !important;
|
||||
}
|
||||
.layout-menu #kt_aside_menu_wrapper, .layout-menu .aside-menu.menu-system, .layout-menu .aside-menu{
|
||||
background-color: var(--primary-button-color-lv) !important;
|
||||
}
|
||||
.menu-system .jstree-node.jstree-open ul, .menu-system .jstree-node{
|
||||
border-color: #818181;
|
||||
}
|
||||
.btn.blue:not(.btn-outline):hover,
|
||||
#create-document-button.floating-button:hover,
|
||||
.sticky .btn.btn-danger:hover,
|
||||
.btn.blue:not(.btn-outline).active.focus, .btn.blue:not(.btn-outline).active:focus, .btn.blue:not(.btn-outline).active:hover, .btn.blue:not(.btn-outline):active.focus, .btn.blue:not(.btn-outline):active:focus, .btn.blue:not(.btn-outline):active:hover, .open > .btn.blue:not(.btn-outline).dropdown-toggle.focus, .open > .btn.blue:not(.btn-outline).dropdown-toggle:focus, .open > .btn.blue:not(.btn-outline).dropdown-toggle:hover,
|
||||
.btn.btn-primary:focus:not(.btn-text), .btn.btn-primary.focus:not(.btn-text),
|
||||
.btn.btn-outline-primary:hover:not(.btn-text):not(:disabled):not(.disabled), .btn.btn-outline-primary:focus:not(.btn-text), .btn.btn-outline-primary.focus:not(.btn-text),
|
||||
.content-user .btn-info:hover,.btn-info:hover,
|
||||
.doc-group-title a.btn:hover, .content-user .add-role:hover,
|
||||
.doc-group-title a.btn:focus, .content-user .add-role:focus,
|
||||
.btn.btn-light-primary:hover,
|
||||
.fc-unthemed .fc-toolbar .pull-right .fc-button:focus, .fc-unthemed .fc-toolbar .pull-right .fc-button:active, .fc-unthemed .fc-toolbar .pull-right .fc-button.fc-button-active
|
||||
{
|
||||
background-color: var(--primary-color-lv) !important;
|
||||
border-color: var(--primary-color-lv) !important;
|
||||
}
|
||||
|
||||
.btn.btn-default:hover:not(.btn-text):not(:disabled):not(.disabled), .btn.btn-default:focus:not(.btn-text), .btn.btn-default.focus:not(.btn-text),
|
||||
.form-control-static.attachment:hover, .form-control-static.attachment:hover .fa,
|
||||
#content-meetings table.scheduler-bg-table th,
|
||||
#content-meetings table.resource-table th, #content-meetings table.scheduler-table th,
|
||||
#content-meetings table.resource-table td, #content-meetings table.scheduler-bg-table td,
|
||||
#content-meetings table.scheduler-table td,
|
||||
#content-meetings .header3-text > div > div,
|
||||
.ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled),
|
||||
.card-more .card-header .nav .nav-link:hover, .card-more .card-header .nav .show > .nav-link i, .card-more .card-header .nav .nav-link:hover:not(.disabled) i{
|
||||
color: var(--primary-color-lv) !important;
|
||||
}
|
||||
|
||||
.btn.btn-light-primary:hover, .card-more .card-header .nav .nav-link.active:hover,
|
||||
#content-meetings .card-header-meeting .btn.btn-light:hover,
|
||||
.more-btn .btn, .more-btn .btn:focus, .more-btn .btn:active
|
||||
{
|
||||
color: #fff !important;
|
||||
}
|
||||
.doc-group-title{
|
||||
color: var(--primary-button-color-lv)!important;
|
||||
}
|
||||
.aside-menu .menu-nav > .menu-item .menu-submenu .menu-item:not(.menu-item-parent):not(.menu-item-open):not(.menu-item-here):not(.menu-item-active):hover > .menu-heading .menu-text,
|
||||
a.text-hover-primary:hover, .text-hover-primary:hover,
|
||||
.header-fixed .top-menu .navbar-nav > li.dropdown .dropdown-menu li a:hover {
|
||||
color: var(--third-color-lv) !important;
|
||||
}
|
||||
.aside-menu .menu-nav > .menu-item .menu-submenu .menu-item:not(.menu-item-parent):not(.menu-item-open):not(.menu-item-here):not(.menu-item-active):hover > .menu-link .menu-text,
|
||||
.aside-left a.menu-link:hover .menu-icon i, .aside-left .menu-item-open .menu-icon i, .aside-left .menu-icon i, .span-total {
|
||||
color: var(--primary-color-lv) !important;
|
||||
}
|
||||
|
||||
/*.card-birthday, .card-birthday + div, .sp-my-task + div {
|
||||
display: none;
|
||||
}*/
|
||||
.sp-my-task + div {
|
||||
display: none;
|
||||
}
|
||||
.lv-contact-detail .font-blue, .navi .navi-item .navi-link:hover,
|
||||
.navi .navi-item .navi-link:hover .navi-icon i,
|
||||
.navi .navi-item .navi-link:hover .navi-text,
|
||||
#content-workspace .detail-dl > a button i {
|
||||
color: var(--third-color-lv) !important;
|
||||
}
|
||||
|
||||
.btn.btn-outline-primary {
|
||||
color: var(--primary-color-lv);
|
||||
background-color: transparent;
|
||||
border-color: var(--primary-color-lv);
|
||||
}
|
||||
|
||||
.btn.btn-hover-light-primary:hover:not(.btn-text):not(:disabled):not(.disabled), .btn.btn-hover-light-primary:focus:not(.btn-text), .btn.btn-hover-light-primary.focus:not(.btn-text) {
|
||||
color: var(--primary-color-lv);
|
||||
background-color: #fdf4fb;
|
||||
border-color: var(--primary-color-lv);
|
||||
}
|
||||
|
||||
.btn.btn-hover-light-primary:hover:not(.btn-text):not(:disabled):not(.disabled) i, .btn.btn-hover-light-primary:focus:not(.btn-text) i, .btn.btn-hover-light-primary.focus:not(.btn-text) i, .btn.btn-light-primary i,
|
||||
.nav .nav-link.active {
|
||||
color: var(--primary-color-lv);
|
||||
}
|
||||
|
||||
.card-more-all .card.card-custom.card-more > .card-header .card-title .card-label, .aside-menu .menu-nav > .menu-item > .menu-link .menu-icon {
|
||||
color: var(--primary-color-lv) !important;
|
||||
}
|
||||
|
||||
.lv-res-lang .btn.green:not(.btn-outline), #txt-um-keyword:focus, .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled), .ant-radio-button-wrapper-checked:not(.ant-radio-button-wrapper-disabled):first-child {
|
||||
border-color: var(--border-color-lv) !important;
|
||||
}
|
||||
|
||||
.lv-res-lang .search-box .input-search-single .btn-search-text-single, .lv-res-lang .search-box #searchStr + span > .btn-search {
|
||||
border-right-color: var(--third-color-lv) !important;
|
||||
}
|
||||
#MainCtrl h3.card-title, .tree_collapse_all i{
|
||||
color: var(--primary-button-color-lv)!important;
|
||||
}
|
||||
.brand-logo img {
|
||||
max-height: 38px;
|
||||
}
|
||||
|
||||
#Schedules button.new, #Schedules .btn-success.btn-send-mail {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.medical-event-title{
|
||||
line-height: 18px;
|
||||
max-height: 54px;
|
||||
overflow: hidden;
|
||||
}
|
||||
/*message***/
|
||||
.lv-box-message.e-dialog .e-dlg-header-content {
|
||||
background-color: var(--primary-color-lv) !important;
|
||||
}
|
||||
|
||||
.lv-box-message.e-dialog .e-dlg-header {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.lv-box-message.e-dialog .e-btn .e-btn-icon.e-icon-dlg-close {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.lv-box-message .e-btn.e-flat.e-primary, .lv-box-message .e-css.e-btn.e-flat.e-primary {
|
||||
color: var(--primary-color-lv) !important;
|
||||
border-radius: 5px !important;
|
||||
}
|
||||
|
||||
.lv-box-message .e-btn.e-flat.e-primary:focus, .lv-box-message .e-css.e-btn.e-flat.e-primary:focus {
|
||||
background-color: #f2f2f2 !important;
|
||||
}
|
||||
|
||||
.lv-box-message.e-dialog .e-btn.e-dlg-closeicon-btn:hover, .lv-box-message .e-dialog .e-btn.e-dlg-closeicon-btn:focus, .lv-box-message .e-dialog .e-btn.e-dlg-closeicon-btn:active {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
/*message*/
|
||||
.card-messages .card-body .scroll-pull > div > div{
|
||||
overflow:unset!important;
|
||||
}
|
||||
.card-messages .card-footer {
|
||||
border-top: none;
|
||||
}
|
||||
.card-messages textarea {
|
||||
overflow:hidden;
|
||||
}
|
||||
.card-messages .thumbs-container{
|
||||
margin-top: 0px;
|
||||
}
|
||||
.card-messages div[class*="react-scroll-to-bottom-"] {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.card-messages .align-items-start, .card-messages .align-items-end {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.card-messages .chat-message .message-text {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.card-messages .align-items-start .bg-light-success {
|
||||
background-color: #F2F6F9 !important;
|
||||
}
|
||||
.card-messages .align-items-end .bg-light-success {
|
||||
background-color: #DBF1FF !important;
|
||||
}
|
||||
.layout-task #hand-over-task,.layout-task #MainCtrl #kt_todo_list h3.card-title .dropdown{
|
||||
display:none;
|
||||
}
|
||||
.layout-task .menuLeft .menu-icon i.fa-folder:before{
|
||||
color: var(--color-i-menu) !important;
|
||||
}
|
||||
.layout-task .detail-tabs.nav-tabs.nav-tabs-line .nav-item{
|
||||
margin-left: 0px;
|
||||
}
|
||||
.layout-task .nav.nav-tabs.nav-tabs-line .nav-item:first-child .nav-link {
|
||||
margin-left: 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.layout-task .nav.nav-tabs.nav-tabs-line .nav-item .nav-link {
|
||||
margin-left: 5px;
|
||||
}
|
||||
/*ntvy*/
|
||||
#open-support {
|
||||
display: none;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
+4
@@ -0,0 +1,4 @@
|
||||
/*! Backstretch - v2.0.4 - 2013-06-19
|
||||
* http://srobbin.com/jquery-plugins/backstretch/
|
||||
* Copyright (c) 2013 Scott Robbin; Licensed MIT */
|
||||
(function(a,d,p){a.fn.backstretch=function(c,b){(c===p||0===c.length)&&a.error("No images were supplied for Backstretch");0===a(d).scrollTop()&&d.scrollTo(0,0);return this.each(function(){var d=a(this),g=d.data("backstretch");if(g){if("string"==typeof c&&"function"==typeof g[c]){g[c](b);return}b=a.extend(g.options,b);g.destroy(!0)}g=new q(this,c,b);d.data("backstretch",g)})};a.backstretch=function(c,b){return a("body").backstretch(c,b).data("backstretch")};a.expr[":"].backstretch=function(c){return a(c).data("backstretch")!==p};a.fn.backstretch.defaults={centeredX:!0,centeredY:!0,duration:5E3,fade:0};var r={left:0,top:0,overflow:"hidden",margin:0,padding:0,height:"100%",width:"100%",zIndex:-999999},s={position:"absolute",display:"none",margin:0,padding:0,border:"none",width:"auto",height:"auto",maxHeight:"none",maxWidth:"none",zIndex:-999999},q=function(c,b,e){this.options=a.extend({},a.fn.backstretch.defaults,e||{});this.images=a.isArray(b)?b:[b];a.each(this.images,function(){a("<img />")[0].src=this});this.isBody=c===document.body;this.$container=a(c);this.$root=this.isBody?l?a(d):a(document):this.$container;c=this.$container.children(".backstretch").first();this.$wrap=c.length?c:a('<div class="backstretch"></div>').css(r).appendTo(this.$container);this.isBody||(c=this.$container.css("position"),b=this.$container.css("zIndex"),this.$container.css({position:"static"===c?"relative":c,zIndex:"auto"===b?0:b,background:"none"}),this.$wrap.css({zIndex:-999998}));this.$wrap.css({position:this.isBody&&l?"fixed":"absolute"});this.index=0;this.show(this.index);a(d).on("resize.backstretch",a.proxy(this.resize,this)).on("orientationchange.backstretch",a.proxy(function(){this.isBody&&0===d.pageYOffset&&(d.scrollTo(0,1),this.resize())},this))};q.prototype={resize:function(){try{var a={left:0,top:0},b=this.isBody?this.$root.width():this.$root.innerWidth(),e=b,g=this.isBody?d.innerHeight?d.innerHeight:this.$root.height():this.$root.innerHeight(),j=e/this.$img.data("ratio"),f;j>=g?(f=(j-g)/2,this.options.centeredY&&(a.top="-"+f+"px")):(j=g,e=j*this.$img.data("ratio"),f=(e-b)/2,this.options.centeredX&&(a.left="-"+f+"px"));this.$wrap.css({width:b,height:g}).find("img:not(.deleteable)").css({width:e,height:j}).css(a)}catch(h){}return this},show:function(c){if(!(Math.abs(c)>this.images.length-1)){var b=this,e=b.$wrap.find("img").addClass("deleteable"),d={relatedTarget:b.$container[0]};b.$container.trigger(a.Event("backstretch.before",d),[b,c]);this.index=c;clearInterval(b.interval);b.$img=a("<img />").css(s).bind("load",function(f){var h=this.width||a(f.target).width();f=this.height||a(f.target).height();a(this).data("ratio",h/f);a(this).fadeIn(b.options.speed||b.options.fade,function(){e.remove();b.paused||b.cycle();a(["after","show"]).each(function(){b.$container.trigger(a.Event("backstretch."+this,d),[b,c])})});b.resize()}).appendTo(b.$wrap);b.$img.attr("src",b.images[c]);return b}},next:function(){return this.show(this.index<this.images.length-1?this.index+1:0)},prev:function(){return this.show(0===this.index?this.images.length-1:this.index-1)},pause:function(){this.paused=!0;return this},resume:function(){this.paused=!1;this.next();return this},cycle:function(){1<this.images.length&&(clearInterval(this.interval),this.interval=setInterval(a.proxy(function(){this.paused||this.next()},this),this.options.duration));return this},destroy:function(c){a(d).off("resize.backstretch orientationchange.backstretch");clearInterval(this.interval);c||this.$wrap.remove();this.$container.removeData("backstretch")}};var l,f=navigator.userAgent,m=navigator.platform,e=f.match(/AppleWebKit\/([0-9]+)/),e=!!e&&e[1],h=f.match(/Fennec\/([0-9]+)/),h=!!h&&h[1],n=f.match(/Opera Mobi\/([0-9]+)/),t=!!n&&n[1],k=f.match(/MSIE ([0-9]+)/),k=!!k&&k[1];l=!((-1<m.indexOf("iPhone")||-1<m.indexOf("iPad")||-1<m.indexOf("iPod"))&&e&&534>e||d.operamini&&"[object OperaMini]"==={}.toString.call(d.operamini)||n&&7458>t||-1<f.indexOf("Android")&&e&&533>e||h&&6>h||"palmGetResource"in d&&e&&534>e||-1<f.indexOf("MeeGo")&&-1<f.indexOf("NokiaBrowser/8.5.0")||k&&6>=k)})(jQuery,window);
|
||||
+14
File diff suppressed because one or more lines are too long
@@ -0,0 +1,117 @@
|
||||
/*!
|
||||
* jQuery Cookie Plugin v1.4.1
|
||||
* https://github.com/carhartl/jquery-cookie
|
||||
*
|
||||
* Copyright 2013 Klaus Hartl
|
||||
* Released under the MIT license
|
||||
*/
|
||||
(function (factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD
|
||||
define(['jquery'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
// CommonJS
|
||||
factory(require('jquery'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function ($) {
|
||||
|
||||
var pluses = /\+/g;
|
||||
|
||||
function encode(s) {
|
||||
return config.raw ? s : encodeURIComponent(s);
|
||||
}
|
||||
|
||||
function decode(s) {
|
||||
return config.raw ? s : decodeURIComponent(s);
|
||||
}
|
||||
|
||||
function stringifyCookieValue(value) {
|
||||
return encode(config.json ? JSON.stringify(value) : String(value));
|
||||
}
|
||||
|
||||
function parseCookieValue(s) {
|
||||
if (s.indexOf('"') === 0) {
|
||||
// This is a quoted cookie as according to RFC2068, unescape...
|
||||
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
||||
}
|
||||
|
||||
try {
|
||||
// Replace server-side written pluses with spaces.
|
||||
// If we can't decode the cookie, ignore it, it's unusable.
|
||||
// If we can't parse the cookie, ignore it, it's unusable.
|
||||
s = decodeURIComponent(s.replace(pluses, ' '));
|
||||
return config.json ? JSON.parse(s) : s;
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function read(s, converter) {
|
||||
var value = config.raw ? s : parseCookieValue(s);
|
||||
return $.isFunction(converter) ? converter(value) : value;
|
||||
}
|
||||
|
||||
var config = $.cookie = function (key, value, options) {
|
||||
|
||||
// Write
|
||||
|
||||
if (value !== undefined && !$.isFunction(value)) {
|
||||
options = $.extend({}, config.defaults, options);
|
||||
|
||||
if (typeof options.expires === 'number') {
|
||||
var days = options.expires, t = options.expires = new Date();
|
||||
t.setTime(+t + days * 864e+5);
|
||||
}
|
||||
|
||||
return (document.cookie = [
|
||||
encode(key), '=', stringifyCookieValue(value),
|
||||
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
|
||||
options.path ? '; path=' + options.path : '',
|
||||
options.domain ? '; domain=' + options.domain : '',
|
||||
options.secure ? '; secure' : ''
|
||||
].join(''));
|
||||
}
|
||||
|
||||
// Read
|
||||
|
||||
var result = key ? undefined : {};
|
||||
|
||||
// To prevent the for loop in the first place assign an empty array
|
||||
// in case there are no cookies at all. Also prevents odd result when
|
||||
// calling $.cookie().
|
||||
var cookies = document.cookie ? document.cookie.split('; ') : [];
|
||||
|
||||
for (var i = 0, l = cookies.length; i < l; i++) {
|
||||
var parts = cookies[i].split('=');
|
||||
var name = decode(parts.shift());
|
||||
var cookie = parts.join('=');
|
||||
|
||||
if (key && key === name) {
|
||||
// If second argument (value) is a function it's a converter...
|
||||
result = read(cookie, value);
|
||||
break;
|
||||
}
|
||||
|
||||
// Prevent storing a cookie that we couldn't decode.
|
||||
if (!key && (cookie = read(cookie)) !== undefined) {
|
||||
result[name] = cookie;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
config.defaults = {};
|
||||
|
||||
$.removeCookie = function (key, options) {
|
||||
if ($.cookie(key) === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must not alter options, thus extending a fresh object...
|
||||
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
|
||||
return !$.cookie(key);
|
||||
};
|
||||
|
||||
}));
|
||||
File diff suppressed because one or more lines are too long
+9
@@ -0,0 +1,9 @@
|
||||
/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la)
|
||||
* Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
|
||||
* and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
|
||||
*
|
||||
* Improved by keenthemes for Metronic Theme
|
||||
* Version: 1.3.2
|
||||
*
|
||||
*/
|
||||
!function(e){jQuery.fn.extend({slimScroll:function(i){var o={width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:.4,alwaysVisible:!1,disableFadeOut:!1,railVisible:!1,railColor:"#333",railOpacity:.2,railDraggable:!0,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:!1,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px",animate:!0},a=e.extend(o,i);return this.each(function(){function o(t){if(u){var t=t||window.event,i=0;t.wheelDelta&&(i=-t.wheelDelta/120),t.detail&&(i=t.detail/3);var o=t.target||t.srcTarget||t.srcElement;e(o).closest("."+a.wrapperClass).is(S.parent())&&r(i,!0),t.preventDefault&&!y&&t.preventDefault(),y||(t.returnValue=!1)}}function r(e,t,i){y=!1;var o=e,r=S.outerHeight()-M.outerHeight();if(t&&(o=parseInt(M.css("top"))+e*parseInt(a.wheelStep)/100*M.outerHeight(),o=Math.min(Math.max(o,0),r),o=e>0?Math.ceil(o):Math.floor(o),M.css({top:o+"px"})),v=parseInt(M.css("top"))/(S.outerHeight()-M.outerHeight()),o=v*(S[0].scrollHeight-S.outerHeight()),i){o=e;var s=o/S[0].scrollHeight*S.outerHeight();s=Math.min(Math.max(s,0),r),M.css({top:s+"px"})}"scrollTo"in a&&a.animate?S.animate({scrollTop:o}):S.scrollTop(o),S.trigger("slimscrolling",~~o),l(),c()}function s(){window.addEventListener?(this.addEventListener("DOMMouseScroll",o,!1),this.addEventListener("mousewheel",o,!1)):document.attachEvent("onmousewheel",o)}function n(){f=Math.max(S.outerHeight()/S[0].scrollHeight*S.outerHeight(),m),M.css({height:f+"px"});var e=f==S.outerHeight()?"none":"block";M.css({display:e})}function l(){if(n(),clearTimeout(p),v==~~v){if(y=a.allowPageScroll,b!=v){var e=0==~~v?"top":"bottom";S.trigger("slimscroll",e)}}else y=!1;return b=v,f>=S.outerHeight()?void(y=!0):(M.stop(!0,!0).fadeIn("fast"),void(a.railVisible&&H.stop(!0,!0).fadeIn("fast")))}function c(){a.alwaysVisible||(p=setTimeout(function(){a.disableFadeOut&&u||h||d||(M.fadeOut("slow"),H.fadeOut("slow"))},1e3))}var u,h,d,p,g,f,v,b,w="<div></div>",m=30,y=!1,S=e(this);if("ontouchstart"in window&&window.navigator.msPointerEnabled&&S.css("-ms-touch-action","none"),S.parent().hasClass(a.wrapperClass)){var E=S.scrollTop();if(M=S.parent().find("."+a.barClass),H=S.parent().find("."+a.railClass),n(),e.isPlainObject(i)){if("height"in i&&"auto"==i.height){S.parent().css("height","auto"),S.css("height","auto");var x=S.parent().parent().height();S.parent().css("height",x),S.css("height",x)}if("scrollTo"in i)E=parseInt(a.scrollTo);else if("scrollBy"in i)E+=parseInt(a.scrollBy);else if("destroy"in i)return M.remove(),H.remove(),void S.unwrap();r(E,!1,!0)}}else{a.height="auto"==i.height?S.parent().height():i.height;var C=e(w).addClass(a.wrapperClass).css({position:"relative",overflow:"hidden",width:a.width,height:a.height});S.css({overflow:"hidden",width:a.width,height:a.height});var H=e(w).addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),M=e(w).addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible?"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius,WebkitBorderRadius:a.borderRadius,zIndex:99}),D="right"==a.position?{right:a.distance}:{left:a.distance};H.css(D),M.css(D),S.wrap(C),S.parent().append(M),S.parent().append(H),a.railDraggable&&M.bind("mousedown",function(i){var o=e(document);return d=!0,t=parseFloat(M.css("top")),pageY=i.pageY,o.bind("mousemove.slimscroll",function(e){currTop=t+e.pageY-pageY,M.css("top",currTop),r(0,M.position().top,!1)}),o.bind("mouseup.slimscroll",function(){d=!1,c(),o.unbind(".slimscroll")}),!1}).bind("selectstart.slimscroll",function(e){return e.stopPropagation(),e.preventDefault(),!1}),"ontouchstart"in window&&window.navigator.msPointerEnabled&&(S.bind("MSPointerDown",function(e){g=e.originalEvent.pageY}),S.bind("MSPointerMove",function(e){e.originalEvent.preventDefault();var t=(g-e.originalEvent.pageY)/a.touchScrollStep;r(t,!0),g=e.originalEvent.pageY})),H.hover(function(){l()},function(){c()}),M.hover(function(){h=!0},function(){h=!1}),S.hover(function(){u=!0,l(),c()},function(){u=!1,c()}),S.bind("touchstart",function(e){e.originalEvent.touches.length&&(g=e.originalEvent.touches[0].pageY)}),S.bind("touchmove",function(e){if(y||e.originalEvent.preventDefault(),e.originalEvent.touches.length){var t=(g-e.originalEvent.touches[0].pageY)/a.touchScrollStep;r(t,!0),g=e.originalEvent.touches[0].pageY}}),n(),"bottom"===a.start?(M.css({top:S.outerHeight()-M.outerHeight()}),r(0,!0)):"top"!==a.start&&(r(e(a.start).position().top,null,!0),a.alwaysVisible||M.hide()),s()}}),this}}),jQuery.fn.extend({slimscroll:jQuery.fn.slimScroll})}(jQuery);
|
||||
+4
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* JavaScript Cookie v2.0.4
|
||||
* https://github.com/js-cookie/js-cookie
|
||||
*
|
||||
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
|
||||
* Released under the MIT license
|
||||
*/
|
||||
!function(e){if("function"==typeof define&&define.amd)define(e);else if("object"==typeof exports)module.exports=e();else{var n=window.Cookies,t=window.Cookies=e();t.noConflict=function(){return window.Cookies=n,t}}}(function(){function e(){for(var e=0,n={};e<arguments.length;e++){var t=arguments[e];for(var o in t)n[o]=t[o]}return n}function n(t){function o(n,r,i){var c;if(arguments.length>1){if(i=e({path:"/"},o.defaults,i),"number"==typeof i.expires){var s=new Date;s.setMilliseconds(s.getMilliseconds()+864e5*i.expires),i.expires=s}try{c=JSON.stringify(r),/^[\{\[]/.test(c)&&(r=c)}catch(a){}return r=encodeURIComponent(String(r)),r=r.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g,decodeURIComponent),n=encodeURIComponent(String(n)),n=n.replace(/%(23|24|26|2B|5E|60|7C)/g,decodeURIComponent),n=n.replace(/[\(\)]/g,escape),document.cookie=[n,"=",r,i.expires&&"; expires="+i.expires.toUTCString(),i.path&&"; path="+i.path,i.domain&&"; domain="+i.domain,i.secure?"; secure":""].join("")}n||(c={});for(var p=document.cookie?document.cookie.split("; "):[],u=/(%[0-9A-Z]{2})+/g,d=0;d<p.length;d++){var f=p[d].split("="),l=f[0].replace(u,decodeURIComponent),m=f.slice(1).join("=");'"'===m.charAt(0)&&(m=m.slice(1,-1));try{if(m=t&&t(m,l)||m.replace(u,decodeURIComponent),this.json)try{m=JSON.parse(m)}catch(a){}if(n===l){c=m;break}n||(c[l]=m)}catch(a){}}return c}return o.get=o.set=o,o.getJSON=function(){return o.apply({json:!0},[].slice.call(arguments))},o.defaults={},o.remove=function(n,t){o(n,"",e(t,{expires:-1}))},o.withConverter=n,o}return n()});
|
||||
@@ -0,0 +1,50 @@
|
||||
var Login = function() {
|
||||
|
||||
var handleLogin = function() {
|
||||
$('.login-form input').keypress(function(e) {
|
||||
if (e.which == 13) {
|
||||
if ($('.login-form').validate().form()) {
|
||||
$('.login-form').submit(); //form validation success, call ajax form submit
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$('.forget-form input').keypress(function(e) {
|
||||
if (e.which == 13) {
|
||||
if ($('.forget-form').validate().form()) {
|
||||
$('.forget-form').submit();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return {
|
||||
//main function to initiate the module
|
||||
init: function() {
|
||||
|
||||
handleLogin();
|
||||
|
||||
// init background slide images
|
||||
$('.login-bg').backstretch([
|
||||
"../Content/theme/assets/pages/img/login/bg1.jpg",
|
||||
"../Content/theme/assets/pages/img/login/bg2.jpg",
|
||||
"../Content/theme/assets/pages/img/login/bg3.jpg"
|
||||
], {
|
||||
fade: 1000,
|
||||
duration: 8000
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}();
|
||||
|
||||
jQuery(document).ready(function() {
|
||||
Login.init();
|
||||
});
|
||||
@@ -0,0 +1,456 @@
|
||||
/* Cubic Bezier Transition */
|
||||
.user-login-5 {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(#006fc7, #7dc2f8);
|
||||
}
|
||||
|
||||
.user-login-5 .bs-reset {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.user-login-5 .forgot-password.text-right {
|
||||
text-align: right;
|
||||
float: right;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.user-login-5 .forgot-password.text-right a {
|
||||
text-decoration: underline;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.user-login-5 .forgot-password.text-right a:hover {
|
||||
color: #ffff99 !important;
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
|
||||
.user-login-5 .login-bg {
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.backstretch img {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.user-login-5 .login-logo {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin-top: 0%;
|
||||
margin-bottom: 8%;
|
||||
}
|
||||
|
||||
.user-login-5 .login-logo.login-6 {
|
||||
top: 80px;
|
||||
left: 80px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container .rem-password {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content,
|
||||
.user-login-5 .login-container .login-social,
|
||||
.user-login-5 .login-container .login-copyright {
|
||||
padding: 0 80px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content {
|
||||
margin-top: 10% !important;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > h1 {
|
||||
font-size: 30px;
|
||||
font-weight: 300;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content p {
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form {
|
||||
margin-top: 40px;
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .form-control {
|
||||
width: 100%;
|
||||
padding: 0 40px 0 40px;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
border: none;
|
||||
border-bottom: 1px solid;
|
||||
border-radius: 30px !important;
|
||||
border-color: #3093e2;
|
||||
color: #696969 !important;
|
||||
font-size: 17px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
input {
|
||||
background-color: rgb(255, 255, 255) !important;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .form-control:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .forgot-password,
|
||||
.user-login-5 .login-container > .login-content > .login-form .login-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .rem-password {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .rem-password > p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .rem-password > .rem-checkbox {
|
||||
border-color: #a4aab2;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .forgot-password {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .forgot-password > a {
|
||||
color: #a4aab2;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .forgot-password > a:hover {
|
||||
color: #337ab7;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content > .login-form .forgot-password > a:focus {
|
||||
color: #a4aab2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.input-icon i {
|
||||
padding: 4px 0 0 6px;
|
||||
}
|
||||
|
||||
.mt-checkbox-outline {
|
||||
float: left;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.dl-lblError {
|
||||
padding-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer .login-social {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer .login-social li {
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer .login-social a {
|
||||
color: #a9b5be;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer .login-social a:hover {
|
||||
color: #337ab7;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer .login-social a:focus {
|
||||
color: #a9b5be;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer .login-copyright {
|
||||
padding-left: 0;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer .login-copyright > p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: #a9b5be;
|
||||
}
|
||||
|
||||
.user-login-5 .alert {
|
||||
margin-top: -60px;
|
||||
}
|
||||
|
||||
.user-login-5 .form-group.has-error {
|
||||
border-bottom: 2px solid #ed6b75 !important;
|
||||
}
|
||||
|
||||
.user-login-5 .form-group.valid {
|
||||
border-bottom: 1px solid #a0a9b4 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 1365px) {
|
||||
.user-login-5 .login-logo.login-6 {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content,
|
||||
.user-login-5 .login-container .login-social,
|
||||
.user-login-5 .login-container .login-copyright {
|
||||
padding: 0 30px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container .login-social {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container .login-copyright {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.user-login-5 {
|
||||
/*min-height: 50vh;*/
|
||||
}
|
||||
|
||||
.user-login-5 .login-bg {
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.user-login-5 .login-logo.login-6 {
|
||||
position: relative;
|
||||
margin: 0 0 40px 0;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container {
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-content .login-form {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container .rem-password {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer {
|
||||
position: relative;
|
||||
margin-top: 40px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.user-login-5 .login-container > .login-footer .login-social li {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.user-login-5 .alert {
|
||||
margin-top: -20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.user-login-5 .login-container > .login-content .text-right {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*** login QLVB ***/
|
||||
.btn.blue, .btn.btn-success {
|
||||
height: 45px;
|
||||
line-height: 45px !important;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
border-radius: 30px !important;
|
||||
font-size: 16px;
|
||||
background-color: #0573cb !important;
|
||||
border-color: #0573cb !important;
|
||||
}
|
||||
|
||||
.btn.blue.btn-forgot-pass {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.btn.btn-outline.yellow-crusta {
|
||||
line-height: 30px !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.btn.blue:hover, .btn.btn-success:hover {
|
||||
background-color: #0573cb !important;
|
||||
border-color: #0573cb !important;
|
||||
}
|
||||
|
||||
.user-login-5 h3.font-green {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.goback {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.goback a {
|
||||
text-decoration: underline;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.goback a {
|
||||
text-decoration: underline !important;
|
||||
color: #ffff99 !important;
|
||||
}
|
||||
|
||||
.validation-summary-errors ul, .validation-summary-errors li {
|
||||
list-style-type: none;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.validation-summary-errors {
|
||||
color: #ec4220 !important;
|
||||
}
|
||||
|
||||
.alert.alert-success {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.login-content .form-control {
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
height: 45px;
|
||||
line-height: 45px;
|
||||
border: none;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-radius: 30px !important;
|
||||
border-top: 1px solid #ccc;
|
||||
background-color: #fff !important;
|
||||
color: #696969 !important;
|
||||
font-size: 17px;
|
||||
margin-bottom: 25px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1.titleChangePass {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
@media (max-height: 767px) {
|
||||
.user-login-5 .login-container > .login-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
/*change pass*/
|
||||
.change-pass .form-group {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.change-pass .form-group label {
|
||||
text-align: left;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.login-content .change-pass .form-control {
|
||||
border-radius: 0px 6px 6px 0px !important;
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.login-content .change-pass span.input-group-addon {
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
|
||||
.change-pass .btn.blue {
|
||||
width: 47% !important;
|
||||
float: right !important;
|
||||
margin-left: 3%;
|
||||
padding: 0 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#lblAccountInfo {
|
||||
color: #EC141B;
|
||||
font-size: 20px;
|
||||
padding-top: 10px;
|
||||
padding: 10px 0px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
display: table;
|
||||
border-collapse: separate;
|
||||
}
|
||||
|
||||
.input-group-addon {
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px 0 0 4px;
|
||||
height: 45px;
|
||||
line-height: 43px;
|
||||
}
|
||||
|
||||
@media (min-width: 1030px) {
|
||||
.login-ees {
|
||||
margin-left: 5px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
#login-slide-show {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login-content {
|
||||
margin-top: 10% !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1029px) {
|
||||
.login-ees {
|
||||
float: left;
|
||||
margin-bottom: 15px;
|
||||
clear: both;
|
||||
}
|
||||
}
|
||||
|
||||
.login-ees i {
|
||||
font-size: 22px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
@@ -92,12 +92,36 @@
|
||||
<!-- Divider -->
|
||||
<hr class="sidebar-divider">
|
||||
|
||||
<!-- Heading -->
|
||||
<div class="sidebar-heading">
|
||||
System
|
||||
</div>
|
||||
|
||||
<!-- Nav Item - Users Collapse Menu -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseUsers"
|
||||
aria-expanded="true" aria-controls="collapseUsers">
|
||||
<i class="fas fa-fw fa-users"></i>
|
||||
<span>Users</span>
|
||||
</a>
|
||||
<div id="collapseUsers" class="collapse" aria-labelledby="headingUsers" data-parent="#accordionSidebar">
|
||||
<div class="bg-white py-2 collapse-inner rounded">
|
||||
<h6 class="collapse-header">User Management:</h6>
|
||||
<a class="collapse-item" th:href="@{/manage/users}">All Users</a>
|
||||
<a class="collapse-item" th:href="@{/manage/users/new}">Add New</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- Divider -->
|
||||
<hr class="sidebar-divider">
|
||||
|
||||
<!-- Heading -->
|
||||
<div class="sidebar-heading">
|
||||
Interface
|
||||
</div>
|
||||
|
||||
<!-- Nav Item - Pages Collapse Menu -->
|
||||
<!-- Nav Item - Components Collapse Menu -->
|
||||
<li class="nav-item">
|
||||
<a class="nav-link collapsed" href="#" data-toggle="collapse" data-target="#collapseTwo"
|
||||
aria-expanded="true" aria-controls="collapseTwo">
|
||||
@@ -145,19 +169,22 @@
|
||||
<li class="nav-item dropdown no-arrow">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="mr-2 d-none d-lg-inline text-gray-600 small">Admin User</span>
|
||||
<span class="mr-2 d-none d-lg-inline text-gray-600 small" sec:authentication="name">Admin User</span>
|
||||
<img class="img-profile rounded-circle"
|
||||
src="https://startbootstrap.github.io/startbootstrap-sb-admin-2/img/undraw_profile.svg">
|
||||
</a>
|
||||
<!-- Dropdown - User Information -->
|
||||
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
|
||||
aria-labelledby="userDropdown">
|
||||
<a class="dropdown-item" href="#">
|
||||
<a class="dropdown-item" th:href="@{/manage/profile}">
|
||||
<i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Profile
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
|
||||
<form method="post" th:action="@{/manage/logout}" id="logoutForm" class="d-none">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
</form>
|
||||
<a class="dropdown-item" href="#" onclick="document.getElementById('logoutForm').submit(); return false;">
|
||||
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
|
||||
Logout
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Cài đặt 2FA / 2FA Setup - SIS Vietnam</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
|
||||
<link th:href="@{/login-assets/css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/bootstrap.min.css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/login-dl.css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/customer-style.css}" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body class="login">
|
||||
<div class="user-login-5">
|
||||
<div class="row bs-reset">
|
||||
<div class="col-md-8 col-sm-6 hidden-xs bs-reset mt-login-5-bsfix">
|
||||
<div class="login-bg" style="background: none; position: relative; z-index: 0;">
|
||||
<div class="backstretch" style="left: 0px; top: 0px; overflow: hidden; margin: 0px; padding: 0px; height: 100vh; width: 100%; z-index: -999998; position: absolute;">
|
||||
<img th:src="@{/login-assets/bg2.jpg}" style="position: absolute; margin: 0px; padding: 0px; border-width: medium; border-style: none; width: 100%; height: 100%; object-fit: cover; z-index: -999999; left: 0px; top: 0px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-6 login-container bs-reset mt-login-5-bsfix">
|
||||
<div class="login-content">
|
||||
<img class="login-logo" th:src="@{/login-assets/logo.png}" style="width:260px; margin-bottom: 10px;">
|
||||
|
||||
<h3 class="font-green">Cài đặt 2FA / 2FA Setup</h3>
|
||||
<p>Hệ thống yêu cầu bạn phải cài đặt 2FA trước khi tiếp tục. Vui lòng sử dụng ứng dụng Google Authenticator để quét mã QR bên dưới.</p>
|
||||
<p>The system requires you to set up 2FA. Please use Google Authenticator to scan the QR code below.</p>
|
||||
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<img th:src="${qrCode}" alt="QR Code" style="width: 200px; border: 1px solid #ccc; padding: 10px; border-radius: 10px; background: white;" />
|
||||
<br/><br/>
|
||||
<small>Hoặc nhập mã bí mật thủ công / Or enter secret manually:</small><br/>
|
||||
<strong th:text="${secret}"></strong>
|
||||
</div>
|
||||
|
||||
<div th:if="${errorMessage}" class="alert alert-danger">
|
||||
<span th:text="${errorMessage}"></span>
|
||||
</div>
|
||||
|
||||
<form th:action="@{/2fa/setup}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<div class="form-group">
|
||||
<input autocomplete="off" class="form-control form-control-solid h-auto py-6 px-6 rounded-lg" required id="code" name="code" placeholder="Mã 6 số / 6-digit Code" type="text" autofocus>
|
||||
</div>
|
||||
|
||||
<div class="pb-lg-0 pb-5 d-flex align-items-center mt-4">
|
||||
<button type="submit" class="btn blue mr-3">Xác nhận & Hoàn tất / Verify & Finish</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="login-footer">
|
||||
<div class="row bs-reset">
|
||||
<div class="col-xs-12 bs-reset">
|
||||
<div class="login-copyright text-right">
|
||||
<p>Copyright © SIS Vietnam 2026</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,61 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Xác minh 2FA / 2FA Verification - SIS Vietnam</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
|
||||
<link th:href="@{/login-assets/css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/bootstrap.min.css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/login-dl.css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/customer-style.css}" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body class="login">
|
||||
<div class="user-login-5">
|
||||
<div class="row bs-reset">
|
||||
<div class="col-md-8 col-sm-6 hidden-xs bs-reset mt-login-5-bsfix">
|
||||
<div class="login-bg" style="background: none; position: relative; z-index: 0;">
|
||||
<div class="backstretch" style="left: 0px; top: 0px; overflow: hidden; margin: 0px; padding: 0px; height: 100vh; width: 100%; z-index: -999998; position: absolute;">
|
||||
<img th:src="@{/login-assets/bg2.jpg}" style="position: absolute; margin: 0px; padding: 0px; border-width: medium; border-style: none; width: 100%; height: 100%; object-fit: cover; z-index: -999999; left: 0px; top: 0px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-6 login-container bs-reset mt-login-5-bsfix">
|
||||
<div class="login-content">
|
||||
<img class="login-logo" th:src="@{/login-assets/logo.png}" style="width:260px; margin-bottom: 30px;">
|
||||
|
||||
<h3 class="font-green">Xác minh 2 bước / Two-Factor Authentication</h3>
|
||||
<p>Vui lòng nhập mã 6 số từ ứng dụng Google Authenticator của bạn.</p>
|
||||
<p>Please enter the 6-digit code from your Google Authenticator app.</p>
|
||||
|
||||
<div th:if="${errorMessage}" class="alert alert-danger">
|
||||
<span th:text="${errorMessage}"></span>
|
||||
</div>
|
||||
|
||||
<form th:action="@{/2fa}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<div class="form-group">
|
||||
<input autocomplete="off" class="form-control form-control-solid h-auto py-6 px-6 rounded-lg" required id="code" name="code" placeholder="Mã 6 số / 6-digit Code" type="text" autofocus>
|
||||
</div>
|
||||
|
||||
<div class="pb-lg-0 pb-5 d-flex align-items-center mt-4">
|
||||
<button type="submit" class="btn blue mr-3">Xác minh / Verify</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="login-footer">
|
||||
<div class="row bs-reset">
|
||||
<div class="col-xs-12 bs-reset">
|
||||
<div class="login-copyright text-right">
|
||||
<p>Copyright © SIS Vietnam 2026</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Đăng nhập/Login - SIS Vietnam</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport">
|
||||
|
||||
<!-- BEGIN GLOBAL MANDATORY STYLES -->
|
||||
<link th:href="@{/login-assets/css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/font-awesome.min.css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/simple-line-icons.min.css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/bootstrap.min.css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/bootstrap-switch.min.css}" rel="stylesheet" type="text/css">
|
||||
<!-- END GLOBAL MANDATORY STYLES -->
|
||||
<!-- BEGIN PAGE LEVEL PLUGINS -->
|
||||
<link th:href="@{/login-assets/select2.min.css}" rel="stylesheet" type="text/css">
|
||||
<link th:href="@{/login-assets/select2-bootstrap.min.css}" rel="stylesheet" type="text/css">
|
||||
<!-- END PAGE LEVEL PLUGINS -->
|
||||
<!-- BEGIN THEME GLOBAL STYLES -->
|
||||
<link th:href="@{/login-assets/components.min.css}" rel="stylesheet" id="style_components" type="text/css">
|
||||
<link th:href="@{/login-assets/plugins.min.css}" rel="stylesheet" type="text/css">
|
||||
<!-- END THEME GLOBAL STYLES -->
|
||||
<!-- BEGIN PAGE LEVEL STYLES -->
|
||||
<link th:href="@{/login-assets/login-dl.css}" rel="stylesheet" type="text/css">
|
||||
<!-- END PAGE LEVEL STYLES -->
|
||||
|
||||
<link rel="shortcut icon" th:href="@{/favicon.ico}">
|
||||
<link th:href="@{/login-assets/customer-style.css}" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body class="login">
|
||||
<div class="user-login-5">
|
||||
<div class="row bs-reset">
|
||||
<div class="col-md-8 col-sm-6 hidden-xs bs-reset mt-login-5-bsfix">
|
||||
<div class="login-bg" style="background: none; position: relative; z-index: 0;">
|
||||
<div class="backstretch" style="left: 0px; top: 0px; overflow: hidden; margin: 0px; padding: 0px; height: 100vh; width: 100%; z-index: -999998; position: absolute;">
|
||||
<img th:src="@{/login-assets/bg2.jpg}" style="position: absolute; margin: 0px; padding: 0px; border-width: medium; border-style: none; width: 100%; height: 100%; object-fit: cover; z-index: -999999; left: 0px; top: 0px;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-6 login-container bs-reset mt-login-5-bsfix">
|
||||
<div class="login-content">
|
||||
|
||||
<img class="login-logo" th:src="@{/login-assets/logo.png}" style="width:260px; margin-bottom: 30px;">
|
||||
|
||||
<div th:if="${param.error}" class="alert alert-danger">
|
||||
<span>Tên đăng nhập hoặc mật khẩu không đúng / Invalid username or password.</span>
|
||||
</div>
|
||||
<div th:if="${param.logout}" class="alert alert-info">
|
||||
<span>Bạn đã đăng xuất thành công / You have been logged out.</span>
|
||||
</div>
|
||||
|
||||
<form th:action="@{/manage/login}" class="form fv-plugins-bootstrap fv-plugins-framework" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<!--begin::Form group-->
|
||||
<div class="form-group fv-plugins-icon-container">
|
||||
<input autocomplete="username" class="form-control form-control-solid h-auto py-6 px-6 rounded-lg" required id="username" name="username" placeholder="Tên đăng nhập / Login Name" type="text" autofocus>
|
||||
</div>
|
||||
<!--end::Form group-->
|
||||
<!--begin::Form group-->
|
||||
<div class="form-group fv-plugins-icon-container">
|
||||
<input autocomplete="current-password" class="form-control form-control-solid h-auto py-6 px-6 rounded-lg" required id="password" name="password" placeholder="Mật khẩu / Password" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group fv-plugins-icon-container">
|
||||
<div class="rem-password">
|
||||
<label class="rememberme mt-checkbox mt-checkbox-outline font-white">
|
||||
<input id="remember-me" name="remember-me" type="checkbox" value="true"> Ghi nhớ / Remember
|
||||
<span></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!--end::Form group-->
|
||||
<!--begin::Action-->
|
||||
<div class="pb-lg-0 pb-5 d-flex align-items-center">
|
||||
<button type="submit" class="btn blue mr-3">Đăng nhập / Login</button>
|
||||
<a th:href="@{/oauth2/authorization/google}" class="btn btn-outline-danger">
|
||||
<i class="fab fa-google"></i> Login with Google
|
||||
</a>
|
||||
</div>
|
||||
<div class="form-group fv-plugins-icon-container mt-4">
|
||||
<div class="pull-left">
|
||||
<a th:href="@{/}" class="btn" style="padding-left: 0;">
|
||||
<i class="fa fa-arrow-left"></i> Quay lại trang chủ / Back to Home
|
||||
</a>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<label class="rememberme mt-checkbox mt-checkbox-outline goback">
|
||||
<a th:href="@{/account/reset/request}" class="btn">
|
||||
Quên mật khẩu / Forgot Password?
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<!--end::Action-->
|
||||
</form>
|
||||
</div>
|
||||
<div class="login-footer">
|
||||
<div class="row bs-reset">
|
||||
<div class="col-xs-12 bs-reset">
|
||||
<div class="login-copyright text-right">
|
||||
<p>Copyright © SIS Vietnam 2026</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script th:src="@{/login-assets/jquery.min.js}" type="text/javascript"></script>
|
||||
<script th:src="@{/login-assets/bootstrap.min.js}" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -82,6 +82,7 @@
|
||||
<div class="card-body">
|
||||
<form id="pageForm" th:action="${isNew} ? @{/manage/pages} : @{/manage/pages/{id}(id=${page.id})}"
|
||||
th:object="${page}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<!-- Validation errors summary -->
|
||||
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger">
|
||||
@@ -118,7 +119,7 @@
|
||||
|
||||
<div class="row">
|
||||
<!-- Page Type -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="pageType" class="font-weight-bold">Page Type <span
|
||||
class="text-danger">*</span></label>
|
||||
@@ -128,8 +129,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layout -->
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="pageLayout" class="font-weight-bold">Layout <span
|
||||
class="text-danger">*</span></label>
|
||||
<select class="form-control" id="pageLayout" th:field="*{layout}">
|
||||
<option th:each="l : ${layouts}" th:value="${l}" th:text="${l}"></option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="pageStatus" class="font-weight-bold">Status <span
|
||||
class="text-danger">*</span></label>
|
||||
@@ -140,7 +152,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Display Order -->
|
||||
<div class="col-md-4">
|
||||
<div class="col-md-3">
|
||||
<div class="form-group">
|
||||
<label for="pageDisplayOrder" class="font-weight-bold">Display Order</label>
|
||||
<input type="number" class="form-control" id="pageDisplayOrder"
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
</a>
|
||||
<form th:action="@{/manage/pages/{id}/delete(id=${page.id})}" method="post" style="display:inline;"
|
||||
onsubmit="return confirm('Are you sure you want to delete this page?');">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<button type="submit" class="btn btn-sm btn-danger" title="Delete">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</button>
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<div class="card-body">
|
||||
<form th:action="${isNew} ? @{/manage/posts/categories} : @{/manage/posts/categories/{id}(id=${category.id})}"
|
||||
th:object="${category}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<!-- Validation errors -->
|
||||
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger alert-sm">
|
||||
@@ -150,6 +151,7 @@
|
||||
<form th:action="@{/manage/posts/categories/{id}/delete(id=${cat.id})}" method="post"
|
||||
style="display:inline;"
|
||||
onsubmit="return confirm('Are you sure you want to delete this category?');">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
|
||||
@@ -131,6 +131,7 @@
|
||||
|
||||
<form id="postForm" th:action="${isNew} ? @{/manage/posts/create} : @{/manage/posts/{id}(id=${post.id})}"
|
||||
th:object="${post}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<!-- Validation errors summary -->
|
||||
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger">
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
</a>
|
||||
<form th:action="@{/manage/posts/{id}/delete(id=${post.id})}" method="post" style="display:inline;"
|
||||
onsubmit="return confirm('Are you sure you want to delete this post?');">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
<div class="card-body">
|
||||
<form th:action="${isNew} ? @{/manage/posts/tags} : @{/manage/posts/tags/{id}(id=${tag.id})}"
|
||||
th:object="${tag}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<!-- Validation errors -->
|
||||
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger alert-sm">
|
||||
@@ -134,6 +135,7 @@
|
||||
<form th:action="@{/manage/posts/tags/{id}/delete(id=${t.id})}" method="post"
|
||||
style="display:inline;"
|
||||
onsubmit="return confirm('Are you sure you want to delete this tag?');">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{fragments/manage-layout}">
|
||||
|
||||
<head>
|
||||
<title th:text="${isNew} ? 'Add New User' : 'Edit User'">User Form</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800" th:text="${isNew} ? 'Add New User' : 'Edit User'">User Form</h1>
|
||||
<a th:href="@{/manage/users}" class="d-none d-sm-inline-block btn btn-sm btn-secondary shadow-sm">
|
||||
<i class="fas fa-arrow-left fa-sm text-white-50"></i> Back to All Users
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary" th:text="${isNew} ? 'Create a New User' : 'Update User Details'">Form</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form th:action="${isNew} ? @{/manage/users} : @{/manage/users/{login}(login=${userDTO.login})}"
|
||||
th:object="${userDTO}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<!-- Form Errors Summary -->
|
||||
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
<li th:each="err : ${#fields.errors('*')}" th:text="${err}"></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<!-- Login -->
|
||||
<div class="form-group">
|
||||
<label for="login" class="font-weight-bold">Login <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="login" th:field="*{login}"
|
||||
th:classappend="${#fields.hasErrors('login')} ? 'is-invalid' : ''"
|
||||
th:readonly="${!isNew}" placeholder="Username" required>
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('login')}" th:errors="*{login}"></div>
|
||||
<small class="form-text text-muted" th:if="${!isNew}">The login cannot be changed after creation.</small>
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="form-group">
|
||||
<label for="email" class="font-weight-bold">Email <span class="text-danger">*</span></label>
|
||||
<input type="email" class="form-control" id="email" th:field="*{email}"
|
||||
th:classappend="${#fields.hasErrors('email')} ? 'is-invalid' : ''"
|
||||
placeholder="user@example.com" required>
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- First Name -->
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="firstName" class="font-weight-bold">First Name</label>
|
||||
<input type="text" class="form-control" id="firstName" th:field="*{firstName}"
|
||||
th:classappend="${#fields.hasErrors('firstName')} ? 'is-invalid' : ''">
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Last Name -->
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="lastName" class="font-weight-bold">Last Name</label>
|
||||
<input type="text" class="form-control" id="lastName" th:field="*{lastName}"
|
||||
th:classappend="${#fields.hasErrors('lastName')} ? 'is-invalid' : ''">
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<!-- Activated -->
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="activated" th:field="*{activated}">
|
||||
<label class="custom-control-label font-weight-bold" for="activated">Activated</label>
|
||||
</div>
|
||||
<small class="form-text text-muted">Deactivated users cannot log in.</small>
|
||||
</div>
|
||||
|
||||
<!-- Language -->
|
||||
<div class="form-group mt-3">
|
||||
<label for="langKey" class="font-weight-bold">Language</label>
|
||||
<select class="form-control" id="langKey" th:field="*{langKey}">
|
||||
<option value="en">English</option>
|
||||
<option value="vi">Vietnamese</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Roles / Authorities -->
|
||||
<div class="form-group mt-3">
|
||||
<label class="font-weight-bold">Profiles (Roles)</label>
|
||||
<div>
|
||||
<div th:each="auth : ${authorities}" class="custom-control custom-checkbox custom-control-inline">
|
||||
<input type="checkbox" class="custom-control-input"
|
||||
th:id="${'auth_' + auth}" th:value="${auth}" th:field="*{authorities}">
|
||||
<label class="custom-control-label" th:for="${'auth_' + auth}" th:text="${auth}"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notice for new user -->
|
||||
<div class="alert alert-info mt-3" th:if="${isNew}">
|
||||
<i class="fas fa-info-circle"></i> A random password and reset key will be generated for new users. They must reset their password to log in.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="d-flex justify-content-between">
|
||||
<a th:href="@{/manage/users}" class="btn btn-secondary">
|
||||
<i class="fas fa-times"></i> Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i>
|
||||
<span th:text="${isNew} ? 'Create User' : 'Update User'">Save</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,126 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{fragments/manage-layout}">
|
||||
|
||||
<head>
|
||||
<title>Users Management</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">User Management</h1>
|
||||
<a th:href="@{/manage/users/new}" class="d-none d-sm-inline-block btn btn-sm btn-primary shadow-sm">
|
||||
<i class="fas fa-plus fa-sm text-white-50"></i> Add New User
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Alerts -->
|
||||
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<span th:text="${successMessage}"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<span th:text="${errorMessage}"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- DataTales Example -->
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">All Registered Users</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Login</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Language</th>
|
||||
<th>Profiles</th>
|
||||
<th>Created Date</th>
|
||||
<th class="text-center" style="width: 150px;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr th:each="user : ${users}">
|
||||
<td><strong th:text="${user.login}">login</strong></td>
|
||||
<td th:text="${user.email}">user@example.com</td>
|
||||
<td>
|
||||
<span th:if="${user.activated}" class="badge badge-success">Activated</span>
|
||||
<span th:unless="${user.activated}" class="badge badge-danger">Deactivated</span>
|
||||
</td>
|
||||
<td th:text="${user.langKey}">en</td>
|
||||
<td>
|
||||
<span th:each="auth : ${user.authorities}" class="badge badge-info mr-1" th:text="${auth}">ROLE</span>
|
||||
</td>
|
||||
<td th:text="${#temporals.format(user.createdDate, 'dd-MM-yyyy HH:mm')}"></td>
|
||||
<td class="text-center">
|
||||
<a th:href="@{/manage/users/{login}/edit(login=${user.login})}" class="btn btn-primary btn-sm" title="Edit User">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-danger btn-sm" title="Delete User"
|
||||
th:data-login="${user.login}"
|
||||
onclick="openDeleteModal(this.getAttribute('data-login'))">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr th:if="${#lists.isEmpty(users)}">
|
||||
<td colspan="7" class="text-center">No users found.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section layout:fragment="scripts">
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="deleteUserModal" tabindex="-1" role="dialog" aria-labelledby="deleteUserModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<form id="deleteUserForm" method="post" action="">
|
||||
<!-- JHipster/Spring Security CSRF token if needed -->
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="deleteUserModalLabel">Confirm Delete</h5>
|
||||
<button class="close" type="button" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Are you sure you want to delete user <strong id="deleteUserLoginDisplay"></strong>?
|
||||
<br>
|
||||
<span class="text-danger small">This action cannot be undone.</span>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" type="button" data-dismiss="modal">Cancel</button>
|
||||
<button type="submit" class="btn btn-danger">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openDeleteModal(login) {
|
||||
document.getElementById('deleteUserLoginDisplay').textContent = login;
|
||||
document.getElementById('deleteUserForm').action = '/manage/users/' + login + '/delete';
|
||||
$('#deleteUserModal').modal('show');
|
||||
}
|
||||
</script>
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
|
||||
layout:decorate="~{fragments/manage-layout}">
|
||||
|
||||
<head>
|
||||
<title>My Profile</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div layout:fragment="content">
|
||||
<!-- Page Heading -->
|
||||
<div class="d-sm-flex align-items-center justify-content-between mb-4">
|
||||
<h1 class="h3 mb-0 text-gray-800">My Profile</h1>
|
||||
</div>
|
||||
|
||||
<!-- Success/Error Alerts for Profile -->
|
||||
<div th:if="${successMessage}" class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<span th:text="${successMessage}"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div th:if="${errorMessage}" class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<span th:text="${errorMessage}"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Success/Error Alerts for Password -->
|
||||
<div th:if="${successPasswordMessage}" class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<span th:text="${successPasswordMessage}"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div th:if="${errorPasswordMessage}" class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
<span th:text="${errorPasswordMessage}"></span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Profile Info Column -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Personal Information</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form th:action="@{/manage/profile/update}" th:object="${userDTO}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<!-- Form Errors Summary -->
|
||||
<div th:if="${#fields.hasErrors('*')}" class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
<li th:each="err : ${#fields.errors('*')}" th:text="${err}"></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Login (Username)</label>
|
||||
<input type="text" class="form-control" th:field="*{login}" readonly>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email" class="font-weight-bold">Email <span class="text-danger">*</span></label>
|
||||
<input type="email" class="form-control" id="email" th:field="*{email}"
|
||||
th:classappend="${#fields.hasErrors('email')} ? 'is-invalid' : ''" required>
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('email')}" th:errors="*{email}"></div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="firstName" class="font-weight-bold">First Name</label>
|
||||
<input type="text" class="form-control" id="firstName" th:field="*{firstName}"
|
||||
th:classappend="${#fields.hasErrors('firstName')} ? 'is-invalid' : ''">
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label for="lastName" class="font-weight-bold">Last Name</label>
|
||||
<input type="text" class="form-control" id="lastName" th:field="*{lastName}"
|
||||
th:classappend="${#fields.hasErrors('lastName')} ? 'is-invalid' : ''">
|
||||
<div class="invalid-feedback" th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="langKey" class="font-weight-bold">Display Language</label>
|
||||
<select class="form-control" id="langKey" th:field="*{langKey}">
|
||||
<option value="en">English</option>
|
||||
<option value="vi">Vietnamese</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="text-right">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Save Profile
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Column -->
|
||||
<div class="col-lg-6 mb-4">
|
||||
<div class="card shadow border-left-warning">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-warning">Change Password</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form th:action="@{/manage/profile/password}" method="post">
|
||||
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
|
||||
|
||||
<div class="form-group">
|
||||
<label for="currentPassword" class="font-weight-bold">Current Password <span class="text-danger">*</span></label>
|
||||
<input type="password" class="form-control" id="currentPassword" name="currentPassword" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="newPassword" class="font-weight-bold">New Password <span class="text-danger">*</span></label>
|
||||
<input type="password" class="form-control" id="newPassword" name="newPassword" minlength="4" maxlength="50" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="confirmPassword" class="font-weight-bold">Confirm New Password <span class="text-danger">*</span></label>
|
||||
<input type="password" class="form-control" id="confirmPassword" name="confirmPassword" minlength="4" maxlength="50" required>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="text-right">
|
||||
<button type="submit" class="btn btn-warning">
|
||||
<i class="fas fa-key"></i> Change Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Reference in New Issue
Block a user