Compare commits

...

4 Commits
dev ... 48-crud

Author SHA1 Message Date
AlexGames73
2728a0ca01 Add: register page, account info page 2019-01-11 23:08:13 +04:00
AlexGames73
cf0e05c918 Fix gradle v2 2019-01-10 21:51:11 +04:00
AlexGames73
bd7e12220b Fix gradle 2019-01-10 21:46:54 +04:00
AlexGames73
383bbc343f Add ReactJS to project 2019-01-10 17:50:23 +04:00
18 changed files with 786 additions and 138 deletions

View File

@ -120,6 +120,12 @@ dependencies {
compile group: 'org.webjars', name: 'jquery', version: '3.3.1-1'
compile group: 'org.webjars.npm', name: 'jquery.easing', version: '1.4.1'
compile group: 'org.webjars', name: 'font-awesome', version: '4.7.0'
compile group: 'org.webjars.npm', name: 'react', version: '16.7.0'
compile group: 'org.webjars.npm', name: 'react-dom', version: '16.7.0'
compile group: 'org.webjars.npm', name: 'material-ui__core', version: '3.5.1'
compile group: 'org.webjars.npm', name: 'babel-standalone', version: '6.26.0'
compile group: 'org.webjars.npm', name: 'babel__runtime', version: '7.2.0'
compile group: 'org.webjars.npm', name: 'js-tokens', version: '4.0.0'
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.5.0'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.5.0'

View File

@ -10,7 +10,7 @@ public class Constants {
public static final String LOGIN_REGEX = "^[_'.@A-Za-z0-9-]*$";
public static final String COOKIES_NAME = "JSESSIONID";
public static final String LOGOUT_URL = "/login?logout";
public static final String LOGOUT_URL = "/login";
public static final String SESSION_ID_ATTR = "sessionId";
public static final int SESSION_TIMEOUT_SECONDS = 30 * 60;

View File

@ -13,6 +13,7 @@ import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import ru.ulstu.user.controller.UserController;
@ -33,17 +34,20 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final AuthenticationSuccessHandler authenticationSuccessHandler;
private final AuthenticationFailureHandler authenticationFailureHandler;
private final LogoutSuccessHandler logoutSuccessHandler;
private final ApplicationProperties applicationProperties;
public SecurityConfiguration(UserService userService,
BCryptPasswordEncoder bCryptPasswordEncoder,
AuthenticationSuccessHandler authenticationSuccessHandler,
AuthenticationFailureHandler authenticationFailureHandler,
LogoutSuccessHandler logoutSuccessHandler,
ApplicationProperties applicationProperties) {
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
this.authenticationSuccessHandler = authenticationSuccessHandler;
this.authenticationFailureHandler = authenticationFailureHandler;
this.logoutSuccessHandler = logoutSuccessHandler;
this.applicationProperties = applicationProperties;
}
@ -75,6 +79,7 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.and()
.formLogin()
.loginPage("/login")
.failureHandler(authenticationFailureHandler)
.successHandler(authenticationSuccessHandler)
.permitAll()
.and()

View File

@ -0,0 +1,41 @@
package ru.ulstu.user.component;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.HashMap;
@Component
public class UserSessionLoginFailureHandler implements AuthenticationFailureHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
HashMap<String, Object> data = new HashMap<>();
data.put("data", null);
HashMap<String, Object> error = new HashMap<>();
error.put("code", HttpStatus.UNAUTHORIZED.value());
error.put("message", "<b>Не удается войти.</b><br>Пожалуйста, проверьте правильность написания <b>логина</b> и <b>пароля</b>.");
data.put("error", error);
response.getOutputStream()
.write(objectMapper.writeValueAsString(data).getBytes(StandardCharsets.UTF_8));
}
}

View File

@ -107,6 +107,13 @@ public class UserController extends OdinController<UserListDto, UserDto> {
return new Response<>(userService.getUserWithRolesById(userId));
}
@GetMapping("/account")
@Secured({UserRoleConstants.ADMIN, UserRoleConstants.USER})
public Response<UserDto> getUserBySession(@RequestParam("session_id") String sessionId){
log.debug("REST: UserController.getUserBySession( {} )", sessionId);
return new Response<>(userSessionService.getUserDtoBySessionId(sessionId));
}
@PostMapping("")
@Secured(UserRoleConstants.ADMIN)

View File

@ -0,0 +1,28 @@
package ru.ulstu.user.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import ru.ulstu.user.service.UserService;
import ru.ulstu.user.service.UserSessionService;
@Controller
public class UserMVCController {
private final Logger log = LoggerFactory.getLogger(UserMVCController.class);
private final UserService userService;
private final UserSessionService userSessionService;
public UserMVCController(UserService userService, UserSessionService userSessionService) {
this.userService = userService;
this.userSessionService = userSessionService;
}
@GetMapping("/acc-info")
public String getAcc(){
return "acc-info";
}
}

View File

@ -10,6 +10,7 @@ import ru.ulstu.core.jpa.OffsetablePageRequest;
import ru.ulstu.core.model.response.PageableItems;
import ru.ulstu.user.error.UserNotFoundException;
import ru.ulstu.user.model.User;
import ru.ulstu.user.model.UserDto;
import ru.ulstu.user.model.UserSession;
import ru.ulstu.user.model.UserSessionListDto;
import ru.ulstu.user.repository.UserSessionRepository;
@ -22,10 +23,12 @@ public class UserSessionService {
private final Logger log = LoggerFactory.getLogger(UserSessionService.class);
private final UserSessionRepository userSessionRepository;
private final UserService userService;
private final UserMapper userMapper;
public UserSessionService(UserSessionRepository userSessionRepository, UserService userService) {
public UserSessionService(UserSessionRepository userSessionRepository, UserService userService, UserMapper userMapper) {
this.userSessionRepository = userSessionRepository;
this.userService = userService;
this.userMapper = userMapper;
}
@Transactional(readOnly = true)
@ -54,4 +57,9 @@ public class UserSessionService {
userSessionRepository.save(userSession);
log.debug("User session {} closed", sessionId);
}
public UserDto getUserDtoBySessionId(String sessionId){
final UserSession userSession = userSessionRepository.findOneBySessionId(sessionId);
return userMapper.userEntityToUserDto(userSession.getUser());
}
}

View File

@ -34,5 +34,5 @@ liquibase.change-log=classpath:db/changelog-master.xml
# Application Settings
ng-tracker.base-url=http://127.0.0.1:8080
ng-tracker.undead-user-login=admin
ng-tracker.dev-mode=true
ng-tracker.dev-mode=false
ng-tracker.use-https=false

View File

@ -56,7 +56,7 @@ function errorHandler(response, callBack, errorCallBack) {
// TODO: add l10n
// showFeedbackMessage(response.error.code + ": " + response.error.message, MessageTypesEnum.DANGER);
if (!isEmpty(errorCallBack)) {
errorCallBack(response.data);
errorCallBack(response.error);
}
throw response.error.code + ": " + response.error.message +
" / Details: " + response.error.data;
@ -121,7 +121,10 @@ function postToRest(url, postData, callBack, completeCallback, errorCallBack) {
return;
}
completeCallback();
}
},
}).fail(function (response) {
errorHandler(response.responseJSON, callBack, errorCallBack);
});
}

View File

@ -0,0 +1,55 @@
const {
LinearProgress,
CircularProgress,
Card,
CardHeader,
CardContent,
FormControl,
FormLabel,
TextField,
Button
} = window['material-ui'];
const { Component } = window['React'];
const React = window['React'];
class Account extends Component{
constructor(){
super();
const session_id = document.cookie.toLowerCase().match(/jsessionid=(.*?).node0/)[1];
$.getJSON("/api/1.0/users/account?session_id=" + session_id,
null, this.responseUserDto);
this.state = {
userDto: null
};
}
responseUserDto = response => {
this.setState({
userDto: response.data
});
};
render(){
return (
this.state.userDto != null ? (
<div style={{width: '75%', margin: 'auto'}}>
<Card>
<CardHeader title={this.state.userDto.firstName + " " + this.state.userDto.lastName}/>
<CardContent>
<p>Login: {this.state.userDto.login}</p>
<p>Email: {this.state.userDto.email}</p>
</CardContent>
</Card>
</div>
) : (
<div className="text-center">
<CircularProgress />
</div>
)
)
}
}
ReactDOM.render(<Account/>, document.getElementById('react-account'));

View File

@ -0,0 +1,85 @@
const {
Card,
CardHeader,
CardContent,
FormControl,
FormLabel,
TextField,
Button
} = window['material-ui'];
const React = window['React'];
const {Component} = React;
class LoginForm extends Component{
state = {
username: "",
password: "",
errorsMessage: ""
};
handleChange = field => event => {
this.setState({
[field]: event.target.value,
["isValid_"+field]: true
});
};
handleSubmit = loginForm => () => {
$.post("/login", $('form.login-form').serialize(),
this.handleResponseAjaxPost(loginForm)("success"))
.fail(this.handleResponseAjaxPost(loginForm)("error"));
};
handleResponseAjaxPost = loginForm => status => response => {
if (status === "success"){
window.location.href = "/index";
} else if (status === "error"){
let responseJSON = JSON.parse(response.responseText);
loginForm.setState({
errorsMessage: responseJSON.error.message,
password: "",
})
}
};
render(){
return (
<Card onKeyPress={e => (e.key === 'Enter' ? this.handleSubmit(this)() : null)} tabIndex="0">
<CardHeader title="Вход"/>
<CardContent>
<form className="login-form">
<FormControl component="fieldset" style={{width: '100%'}}>
<FormLabel error={true}>
<div
className="Container"
dangerouslySetInnerHTML={{__html: this.state.errorsMessage}}>
</div>
</FormLabel>
<TextField
label="Логин"
value={this.state.username}
onChange={this.handleChange("username")}
margin="normal"
type="text"
InputProps={{name: "username"}}
/>
<TextField
label="Пароль"
value={this.state.password}
onChange={this.handleChange("password")}
margin="normal"
type="password"
InputProps={{name: "password"}}
/>
<Button
variant="contained"
onClick={this.handleSubmit(this)}
style={{margin: 'auto'}}
>Войти</Button>
</FormControl>
</form>
</CardContent>
</Card>
);
}
}

View File

@ -0,0 +1,294 @@
const {
Card,
CardHeader,
CardContent,
FormControl,
FormLabel,
TextField,
Button,
InputAdornment,
IconButton,
Icon,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions
} = window['material-ui'];
const { Component } = window['React'];
const React = window['React'];
class RegisterForm extends Component{
validate(field, state) {
switch (field) {
case "login":
return (
(state[field].length >= 4) &&
/^[_'.@A-Za-z0-9-]*$/.test(state[field])
);
case "password":
return (
(state[field].length >= 6)
);
case "passwordConfirm":
return (
(state[field].length >= 6) &&
(state[field] === state["password"])
);
case "email":
return (
(/.+@.+\..+/i.test(state[field]))
);
case "firstName":
return (
(state[field].length >= 2)
);
case "lastName":
return (
(state[field].length >= 2)
);
}
}
fields = ["login", "password", "passwordConfirm", "email", "firstName", "lastName"];
labels = {
login: "Логин",
password: "Пароль",
passwordConfirm: "Подтверждение пароля",
email: "Email",
firstName: "Имя",
lastName: "Фамилия",
};
typeInputs = {
login: "text",
password: "password",
passwordConfirm: "password",
email: "email",
firstName: "text",
lastName: "text",
};
dialogContents = {
login: (
<DialogContentText>
Длина логина должна быть <b>не менее 4 символов</b> и <b>не превышать 50 символов</b>.<br/>
Логин может состоять из:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;1. Строчных или заглавных букв латинского алфавита<br/>
&nbsp;&nbsp;&nbsp;&nbsp;2. Цифр<br/>
&nbsp;&nbsp;&nbsp;&nbsp;3. знаков препинания <b>_'.@-</b><br/>
</DialogContentText>
),
password: (
<DialogContentText>
Длина пароля должна быть <b>не менее 6 символов</b> и <b>не превышать 50 символов</b>.
</DialogContentText>
),
passwordConfirm: (
<DialogContentText>
Пароли должны совпадать и соответствовать требованиям пароля
</DialogContentText>
),
email: (
<DialogContentText>
Почта должна соответвовать примеру: <b>example@domain.com</b>
</DialogContentText>
),
firstName: (
<DialogContentText>
Длина имени должна быть <b>не менее 2 символов</b> и <b>не превышать 50 символов</b>.
</DialogContentText>
),
lastName: (
<DialogContentText>
Длина Фамилии должна быть <b>не менее 2 символов</b> и <b>не превышать 50 символов</b>.
</DialogContentText>
),
};
state = {
isValid_login: true,
isValid_password: true,
isValid_passwordConfirm: true,
isValid_email: true,
isValid_firstName: true,
isValid_lastName: true,
login: "",
password: "",
passwordConfirm: "",
email: "",
firstName: "",
lastName: "",
error_login: "",
error_password: "",
error_passwordConfirm: "",
error_email: "",
error_firstName: "",
error_lastName: "",
isComplete: false,
errorsMessage: "",
infoAbout: "",
infoIsOpen: false,
};
handleChange = field => event => {
this.setState({
[field]: event.target.value,
["isValid_"+field]: true
});
};
isValid = state => {
let result = true;
this.fields.forEach(field => {
result = result && state["isValid_"+field];
});
return result;
};
registerFormToJson = function () {
let formData = {};
$(".register-form").find(":input").not(":button").each(function () {
if ($(this).val() == null || $(this).val() === "") {
return true;
}
formData[$(this).attr("name")] = $(this).val();
});
return JSON.stringify(formData);
};
handleSubmit = registerForm => () => {
let valids = {};
this.fields.forEach(field => {
valids["isValid_"+field]=this.validate(field, registerForm.state)
});
this.setState(valids);
if (this.isValid(valids)){
postToRest("/api/1.0/users/register", this.registerFormToJson(), null,
this.handleResponseAjaxPost(registerForm)("success"),
this.handleResponseAjaxPost(registerForm)("error"));
}
};
handleResponseAjaxPost = registerForm => status => response => {
if (status === "success"){
registerForm.setState({
isComplete: true
});
} else if (status === "error"){
switch (response.code) {
case 103:
registerForm.setState({
errorsMessage: response.message,
error_login: "Пользователь с таким логином уже существует",
isValid_login: false
});
break;
case 102:
registerForm.setState({
errorsMessage: response.message,
error_email: "Пользователь с таким адресом уже существует",
isValid_email: false
});
break;
default:
registerForm.setState({
errorsMessage: response.message
});
break;
}
}
};
handleClickInfo = field => () => {
this.setState({
infoAbout: field,
infoIsOpen: true,
});
};
handleCloseInfo = () => {
this.setState({
infoIsOpen: false
})
};
getForm = () => {
return (
<form className="register-form">
<FormControl component="fieldset" style={{width: '100%'}}>
<FormLabel error={true}>
<div
className="Container"
dangerouslySetInnerHTML={{__html: this.state.errorsMessage}}>
</div>
</FormLabel>
{this.fields.map(field => (
<TextField
label={this.labels[field]}
value={this.state[field]}
onChange={this.handleChange(field)}
margin="normal"
error={!this.state["isValid_"+field]}
helperText={!this.state["isValid_"+field] ? this.state["error_"+field] : ""}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
aria-label="Правила"
onClick={this.handleClickInfo(field)}
>
<Icon>info</Icon>
</IconButton>
</InputAdornment>
),
name: field
}}
type={this.typeInputs[field]}
/>
))}
<Dialog
open={this.state.infoIsOpen}
onClose={this.handleCloseInfo}
aria-labelledby="form-dialog-title"
>
<DialogTitle id="form-dialog-title">{this.labels[this.state.infoAbout]}</DialogTitle>
<DialogContent>
{this.dialogContents[this.state.infoAbout]}
</DialogContent>
<DialogActions>
<Button onClick={this.handleCloseInfo} color="primary">
Закрыть
</Button>
</DialogActions>
</Dialog>
<Button
variant="contained"
onClick={this.handleSubmit(this)}
style={{margin: 'auto'}}
disabled={!this.isValid(this.state)}
>Зарегистрироваться</Button>
</FormControl>
</form>
);
};
getCompleteContent = () => {
return (
<div className="text-center">
<h5>Вам на почту <b>{this.state.email}</b> придет письмо с активацией аккаунта в течении нескольких минут.</h5>
</div>
);
};
render(){
return (
<Card onKeyPress={e => (e.key === 'Enter' ? this.handleSubmit(this)() : null)} tabIndex="0">
<CardHeader title={this.state.isComplete ? "Вы успешно зарегистрировались" : "Регистрация"}/>
<CardContent>
{this.state.isComplete ?
this.getCompleteContent() :
this.getForm()}
</CardContent>
</Card>
);
}
}

View File

@ -0,0 +1,52 @@
const {Button, Icon} = window['material-ui'];
const {render} = window['ReactDOM'];
const React = window['React'];
const {Component} = React;
class LoginPage extends Component{
state = {
isLogin: true,
isRegister: false
};
handleOpenLogin = loginPage => () => {
loginPage.setState({
isLogin: true,
isRegister: false
});
};
handleOpenRegister = loginPage => () => {
loginPage.setState({
isLogin: false,
isRegister: true
});
};
render() {
this.form = null;
if (this.state.isLogin)
this.form = <LoginForm/>;
else if (this.state.isRegister)
this.form = <RegisterForm/>;
if ($('.isAuth').length === 0)
return (
<div className="login-page" style={{width: '50%', margin: 'auto'}}>
<div className="text-center" style={{marginBottom: '10px'}}>
<Button variant="contained" onClick={this.handleOpenLogin(this)} style={{marginRight: '10px'}}>Авторизация</Button>
<Button variant="contained" onClick={this.handleOpenRegister(this)}>Регистрация</Button>
</div>
{this.form}
</div>
);
else
return (
<div className="col-lg-12 text-center">
<h2 className="section-heading text-uppercase">Ты уже авторизован</h2>
<Button variant="extendedFab" href="/"><Icon>navigation</Icon>Вернуться на главную</Button>
</div>
);
}
}
render(<LoginPage/>, document.getElementById("loginForm"));

View File

@ -0,0 +1,118 @@
const {
Drawer, AppBar, Toolbar,
IconButton, Icon,
Typography, Menu, MenuItem,
List, ListItem, ListItemIcon,
ListItemText, Divider, ListSubheader,
withStyles
} = window['material-ui'];
const styles = {
root: {
flexGrow: 1,
},
grow: {
flexGrow: 1,
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
};
class Navbar extends React.Component {
state = {
anchorEl: null,
left: false
};
handleMenu = event => {
this.setState({ anchorEl: event.currentTarget });
};
handleDrawer = value => () => {
this.setState({
left: value
});
};
handleClose = () => {
this.setState({ anchorEl: null });
};
render() {
const { classes } = this.props;
const { anchorEl } = this.state;
const open = Boolean(anchorEl);
let accountContent = null;
if ($('.isAuth').length !== 0)
accountContent = (
<div className="account-menu">
<IconButton
aria-owns={open ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={this.handleMenu}
color="inherit"
>
{$('.isAuth')[0].textContent} <Icon>account_circle</Icon>
</IconButton>
<Menu
id="menu-appbar"
anchorEl={anchorEl}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
open={open}
onClose={this.handleClose}
>
<MenuItem onClick={()=>{window.location.href="/acc-info"}}>Мой аккаунт</MenuItem>
<MenuItem onClick={()=>{window.location.href="/logout"}}>Выход</MenuItem>
</Menu>
</div>
);
return (
<div className={classes.root}>
<AppBar>
<Toolbar>
<IconButton className={classes.menuButton} onClick={this.handleDrawer(true)} color="inherit" aria-label="Menu">
<Icon>menu</Icon>
</IconButton>
<Drawer open={this.state.left} onClose={this.handleDrawer(false)}>
<div className={classes.list} style={{width: '16rem'}}>
<List subheader={<ListSubheader component="div">Ссылки</ListSubheader>}>
{[
{label: 'НИО-17', link: '#landing'},
{label: 'Сайт кафедры', link: 'http://is.ulstu.ru'},
{label: 'КИАС РФФИ', link: 'https://kias.rfbr.ru/'}
].map(item => (
<ListItem button key={item.label} onClick={() => {window.location.href=item.link}}>
<ListItemIcon><Icon>label_important</Icon></ListItemIcon>
<ListItemText primary={item.label} />
</ListItem>
))}
</List>
</div>
</Drawer>
<Typography variant="h6" color="inherit" className={classes.grow}>
NG-Tracker
</Typography>
{accountContent}
</Toolbar>
</AppBar>
</div>
);
}
}
Navbar.propTypes = {
classes: PropTypes.object.isRequired,
};
ReactDOM.render(React.createElement(withStyles(styles)(Navbar), null, null), document.getElementById('react-navbar'));

View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:form="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
layout:decorator="default">
<div class="container" layout:fragment="content">
<section id="react-account">
</section>
</div>
<div layout:fragment="react-scripts">
<script type="text/babel" src="/js/react/accountPage/index.js"></script>
</div>
</html>

View File

@ -12,11 +12,6 @@
class="fa fa-plane fa-4" aria-hidden="true"></i> Balance</span></a>
</div>
</nav>
<div class="container" layout:fragment="content">
<div class="margined-top-10">
<a href="/login" class="btn btn-success btn-block" role="button">Вернуться к странице входа</a>
</div>
</div>
<th:block layout:fragment="data-scripts">
<script type="text/javascript">
/*<![CDATA[*/
@ -26,8 +21,9 @@
showFeedbackMessage("Ключ активации не задан", MessageTypesEnum.DANGER);
return;
}
postToRest(urlUsersActivate + "?key=" + key, null,
postToRest("/api/1.0/users/activate?key=" + key, null,
function () {
window.location.href = "/";
showFeedbackMessage("Пользователь успешно активирован");
}
);

View File

@ -1,5 +1,7 @@
<!DOCTYPE html>
<html lang="ru"
<html lang="ru" data-framework="javascript"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
@ -20,6 +22,7 @@
<link rel="stylesheet" type="text/css" href="/css/google/kaushan.css"/>
<link rel="stylesheet" type="text/css" href="/css/google/droid.css"/>
<link rel="stylesheet" type="text/css" href="/css/google/roboto.css"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/>
<!-- Custom styles for this template -->
<link rel="stylesheet" href="/css/agency.css"/>
@ -36,33 +39,35 @@
<body id="page-top">
<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark fixed-top navbar-shrink" id="mainNav">
<div class="container">
<a class="navbar-brand js-scroll-trigger" href="/">NG-Tracker</a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"
data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false"
aria-label="Toggle navigation">
Menu
<i class="fa fa-bars"></i>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav text-uppercase ml-auto">
<li class="nav-item">
<a class="nav-link js-scroll-trigger" target="_blank" href="#landing">НИО-17</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" target="_blank" href="http://is.ulstu.ru">Сайт кафедры</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" target="_blank" href="https://kias.rfbr.ru/">КИАС РФФИ</a>
</li>
<li class="nav-item">
<a class="nav-link js-scroll-trigger" href="#logout">Выход</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="isAuth" sec:authorize="isAuthenticated()" style="display: none" sec:authentication="name"></div>
<div id="react-navbar"></div>
<!--<nav class="navbar navbar-expand-lg navbar-dark fixed-top navbar-shrink" id="mainNav">-->
<!--<div class="container">-->
<!--<a class="navbar-brand js-scroll-trigger" href="/">NG-Tracker</a>-->
<!--<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse"-->
<!--data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false"-->
<!--aria-label="Toggle navigation">-->
<!--Menu-->
<!--<i class="fa fa-bars"></i>-->
<!--</button>-->
<!--<div class="collapse navbar-collapse" id="navbarResponsive">-->
<!--<ul class="navbar-nav text-uppercase ml-auto">-->
<!--<li class="nav-item">-->
<!--<a class="nav-link js-scroll-trigger" target="_blank" href="#landing">НИО-17</a>-->
<!--</li>-->
<!--<li class="nav-item">-->
<!--<a class="nav-link js-scroll-trigger" target="_blank" href="http://is.ulstu.ru">Сайт кафедры</a>-->
<!--</li>-->
<!--<li class="nav-item">-->
<!--<a class="nav-link js-scroll-trigger" target="_blank" href="https://kias.rfbr.ru/">КИАС РФФИ</a>-->
<!--</li>-->
<!--<li class="nav-item" sec:authorize="isAuthenticated()">-->
<!--<a class="nav-link js-scroll-trigger" href="/logout">Выход</a>-->
<!--</li>-->
<!--</ul>-->
<!--</div>-->
<!--</div>-->
<!--</nav>-->
<div class="container-fluid">
<div class="container">
<ul id="messages" class="feedback-panel">
@ -91,6 +96,20 @@
</footer>
<script src="/js/core.js"></script>
<div layout:fragment="data-scripts"></div>
<!-- React -->
<script src="/webjars/react/16.7.0/umd/react.production.min.js"></script>
<script src="/webjars/react-dom/16.7.0/umd/react-dom.production.min.js"></script>
<script src="/webjars/material-ui__core/3.5.1/umd/material-ui.production.min.js"></script>
<script src="/webjars/prop-types/15.6.2/prop-types.min.js"></script>
<script src="/webjars/babel-standalone/6.26.0/babel.min.js"></script>
<div id="react-scripts">
<div layout:fragment="react-scripts"></div>
<script type="text/babel" src="/js/react/mainPage/navbar.js"></script>
</div>
<!-- /React -->
<!-- Yandex.Metrika counter -->
<script type="text/javascript" >
(function (d, w, c) {

View File

@ -1,7 +1,9 @@
<!DOCTYPE html>
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="default">
<head>
</head>
<body>
@ -12,105 +14,16 @@
</div>
</nav>
<div class="container" layout:fragment="content">
<ul class="nav nav-tabs">
<li class="active"><a href="#signin">Вход в систему</a></li>
<li><a href="#register">Регистрация</a></li>
</ul>
<div class="tab-content">
<div id="signin" class="tab-pane active">
<form th:action="@{/login}" method="post" class="margined-top-10">
<fieldset>
<div class="form-group">
<input type="text" name="username" id="username" class="form-control"
placeholder="Логин" required="true" autofocus="true"/>
<div class="isAuth" sec:authorize="isAuthenticated()" style="display: none"></div>
<section id="loginForm">
</section>
</div>
<div layout:fragment="react-scripts" th:remove="tag">
<script type="text/babel" src="/js/react/loginPage/components/login.js"></script>
<script type="text/babel" src="/js/react/loginPage/components/register.js"></script>
<script type="text/babel" src="/js/react/loginPage/index.js"></script>
</div>
<div class="form-group">
<input type="password" name="password" id="password" class="form-control"
placeholder="Пароль" required="true"/>
</div>
<button type="submit" class="btn btn-success btn-block">Войти</button>
<div class="form-group">
<small class="form-text text-muted">
<a href="/resetRequest">Забыли пароль?</a>
</small>
</div>
</fieldset>
</form>
</div>
<div id="register" class="tab-pane">
<form id="register-form" class="margined-top-10">
<fieldset>
<div class="form-group">
<input type="text" name="login" id="login" class="form-control"
placeholder="Логин" required="true" autofocus="true"
minlength="4" maxlength="50"/>
</div>
<div class="form-group">
<input type="text" name="firstName" id="firstName" class="form-control"
placeholder="Имя" required="true" autofocus="true"
minlength="2" maxlength="50"/>
</div>
<div class="form-group">
<input type="text" name="lastName" id="lastName" class="form-control"
placeholder="Фамилия" required="true" autofocus="true"
minlength="2" maxlength="50"/>
</div>
<div class="form-group">
<input type="text" name="email" id="email" class="form-control"
placeholder="E-Mail" required="true" autofocus="true"
minlength="5" maxlength="100"/>
</div>
<div class="form-group">
<input type="password" name="password" id="password1" class="form-control"
placeholder="Пароль" required="true" maxlength="50"/>
</div>
<div class="form-group">
<input type="password" name="passwordConfirm" id="password2" class="form-control"
placeholder="Пароль (подтверждение)" required="true" maxlength="50"/>
</div>
<button type="submit" class="btn btn-info btn-block">Создать запись</button>
</fieldset>
</form>
</div>
</div>
</div>
<th:block layout:fragment="data-scripts">
<script type="text/javascript">
/*<![CDATA[*/
$(document).ready(function () {
if (isUrlVarExists('error')) {
showFeedbackMessage('Ошибка входа в систему', 'danger');
}
if (isUrlVarExists('logout')) {
showFeedbackMessage('Выход из системы произведен');
}
registerFormToJson = function () {
var formData = {};
$("#register-form").find(":input").not(":button").each(function () {
if ($(this).val() == null || $(this).val() === "") {
return true;
}
formData[$(this).attr("name")] = $(this).val();
});
return JSON.stringify(formData);
};
registerUser = function () {
postToRest(urlUsersRegister, registerFormToJson(), function (data) {
showFeedbackMessage("Пользователь успешно создан");
$("#register-form").find(":input").not(":button").val("");
});
};
$("#register-form").submit(function () {
registerUser();
return false;
});
});
/*]]>*/
</script>
</th:block>
<th:block layout:fragment="scripts">
<script src="/js/hashable-tabs.js"></script>
</th:block>
</body>
</html>