diff --git a/gringotts-web/src/main/java/com/eshore/gringotts/web/WebApplication.java b/gringotts-web/src/main/java/com/eshore/gringotts/web/WebApplication.java index 53fb8de..595236d 100644 --- a/gringotts-web/src/main/java/com/eshore/gringotts/web/WebApplication.java +++ b/gringotts-web/src/main/java/com/eshore/gringotts/web/WebApplication.java @@ -1,6 +1,7 @@ package com.eshore.gringotts.web; import com.blinkfox.fenix.EnableFenix; +import com.eshore.gringotts.web.domain.user.service.UserService; import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; @@ -25,12 +26,19 @@ import org.springframework.scheduling.annotation.EnableAsync; @EnableConfigurationProperties @EnableEncryptableProperties public class WebApplication implements ApplicationRunner { + private final UserService userService; + + public WebApplication(UserService userService) { + this.userService = userService; + } + public static void main(String[] args) { SpringApplication.run(WebApplication.class, args); } @Override public void run(ApplicationArguments args) throws Exception { - // Test Something + // 初始化系统管理员 + userService.initial(); } } diff --git a/gringotts-web/src/main/java/com/eshore/gringotts/web/configuration/SaTokenConfiguration.java b/gringotts-web/src/main/java/com/eshore/gringotts/web/configuration/SaTokenConfiguration.java index ea5b1e6..940c4ee 100644 --- a/gringotts-web/src/main/java/com/eshore/gringotts/web/configuration/SaTokenConfiguration.java +++ b/gringotts-web/src/main/java/com/eshore/gringotts/web/configuration/SaTokenConfiguration.java @@ -2,6 +2,9 @@ package com.eshore.gringotts.web.configuration; import cn.dev33.satoken.interceptor.SaInterceptor; import cn.dev33.satoken.stp.StpUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -11,11 +14,18 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * @author lanyuanxiaoyao * @date 2024-11-14 */ -// @Configuration +@Configuration public class SaTokenConfiguration implements WebMvcConfigurer { + private static final Logger logger = LoggerFactory.getLogger(SaTokenConfiguration.class); + @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new SaInterceptor(handler -> StpUtil.checkLogin())) + registry.addInterceptor( + new SaInterceptor(handler -> { + logger.info("Handler {}", handler); + StpUtil.checkLogin(); + }) + ) .addPathPatterns("/**") .excludePathPatterns("/*.html") .excludePathPatterns("/assets/**") diff --git a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserController.java b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserController.java index f3dd79e..fb41909 100644 --- a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserController.java +++ b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserController.java @@ -41,19 +41,18 @@ public class UserController { @PostMapping("/register") public void register(@RequestBody RegisterRequest request) { logger.info("Register request: {}", request); - userService.register(request); + userService.register(request.username, request.password, request.role); } @PostMapping("/login") public AmisResponse login(@RequestBody LoginRequest request) { logger.info("Login request: {}", request); - return AmisResponse.responseSuccess(userService.login(request)); + return AmisResponse.responseSuccess(userService.login(request.username, request.password)); } - @GetMapping("/logout/{username}") - public void logout(@PathVariable("username") String username) { - logger.info("Logout request: {}", username); - userService.logout(username); + @GetMapping("/logout") + public void logout() { + userService.logout(); } @GetMapping("/exists_username/{username}") @@ -64,14 +63,14 @@ public class UserController { } @Data - public static class LoginRequest { + public static final class LoginRequest { private String username; private String password; private String checkCode; } @Data - public static class RegisterRequest { + public static final class RegisterRequest { private String username; private String password; private User.Role role; diff --git a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserManagementController.java b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserManagementController.java index fd400de..9a09a8b 100644 --- a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserManagementController.java +++ b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/controller/UserManagementController.java @@ -3,10 +3,13 @@ package com.eshore.gringotts.web.domain.user.controller; import com.eshore.gringotts.web.configuration.amis.AmisListResponse; import com.eshore.gringotts.web.configuration.amis.AmisResponse; import com.eshore.gringotts.web.domain.user.service.UserService; +import lombok.Data; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -32,6 +35,17 @@ public class UserManagementController { return AmisListResponse.responseListData(userService.list()); } + @GetMapping("/detail/{username}") + public AmisResponse detail(@PathVariable String username) { + return AmisResponse.responseSuccess(userService.detail(username)); + } + + @PostMapping("/change_password") + public AmisResponse changePassword(@RequestBody ChangePasswordRequest request) { + userService.changePassword(request.oldPassword, request.newPassword); + return AmisResponse.responseSuccess(); + } + @GetMapping("/disable/{username}") public AmisResponse disable(@PathVariable String username) { userService.disable(username); @@ -43,4 +57,10 @@ public class UserManagementController { userService.enable(username); return AmisResponse.responseSuccess(); } + + @Data + public static final class ChangePasswordRequest { + private String oldPassword; + private String newPassword; + } } diff --git a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/service/UserService.java b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/service/UserService.java index b46384e..b45a632 100644 --- a/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/service/UserService.java +++ b/gringotts-web/src/main/java/com/eshore/gringotts/web/domain/user/service/UserService.java @@ -5,7 +5,6 @@ import cn.dev33.satoken.stp.SaTokenInfo; import cn.dev33.satoken.stp.StpUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; -import com.eshore.gringotts.web.domain.user.controller.UserController; import com.eshore.gringotts.web.domain.user.entity.User; import com.eshore.gringotts.web.domain.user.repository.UserRepository; import java.time.LocalDateTime; @@ -33,11 +32,33 @@ public class UserService { this.userRepository = userRepository; } + private static String encryptPassword(String password) { + String salt = "tY2gNdkt7x%%HcCAFc"; + return SecureUtil.sha256(StrUtil.format("{}{}", salt, password)); + } + + /** + * 当数据库里没有用户的时候,增加一个默认的系统管理员 + */ + public void initial() { + if (userRepository.count() == 0) { + User user = new User(); + user.setUsername("administrator@eshore.com"); + user.setPassword(encryptPassword("administrator")); + user.setRole(User.Role.ADMINISTRATOR); + user.setState(User.State.NORMAL); + userRepository.save(user); + } + } + + /** + * 获取当前登陆账号状态 + */ public UserInformation state() { try { - // long id = StpUtil.getLoginIdAsLong(); - // User user = userRepository.findById(id).orElseThrow(LoginNotFoundException::new); - User user = userRepository.findByUsername("lanyuanxiaoyao@qq.com").orElseThrow(LoginNotFoundException::new); + long id = StpUtil.getLoginIdAsLong(); + User user = userRepository.findById(id).orElseThrow(LoginNotFoundException::new); + // User user = userRepository.findByUsername("administrator@eshore.com").orElseThrow(LoginNotFoundException::new); StpUtil.login(user.getId()); return new UserInformation(user, StpUtil.getTokenInfo()); } catch (NotLoginException e) { @@ -45,13 +66,16 @@ public class UserService { } } - public UserInformation login(UserController.LoginRequest request) { - User user = userRepository.findByUsername(request.getUsername()).orElseThrow(LoginFailureException::new); + /** + * 登陆操作 + */ + public UserInformation login(String username, String password) { + User user = userRepository.findByUsername(username).orElseThrow(LoginFailureException::new); if (user.getState() == User.State.CHECKING) { throw new LoginFailureByCheckingException(); } else if (user.getState() == User.State.DISABLED) { throw new LoginFailureByDisabledException(); - } else if (!StrUtil.equals(encryptPassword(request.getPassword()), user.getPassword())) { + } else if (!StrUtil.equals(encryptPassword(password), user.getPassword())) { throw new LoginFailureException(); } else { StpUtil.login(user.getId()); @@ -59,23 +83,41 @@ public class UserService { } } - public void logout(String username) { - User user = userRepository.findByUsername(username).orElseThrow(LogoutFailureException::new); - StpUtil.login(user.getId()); + /** + * 登出操作 + */ + public void logout() { + StpUtil.logout(); } - public void register(UserController.RegisterRequest request) { - userRepository.save(toUser(request)); + /** + * 注册操作 + */ + public void register(String username, String password, User.Role role) { + User user = new User(); + user.setUsername(username); + user.setPassword(encryptPassword(password)); + user.setRole(role); + userRepository.save(user); } + /** + * 是否存在用户名 + */ public boolean exitsUsername(String username) { return userRepository.existsByUsername(username); } + /** + * 禁用账号 + */ public void disable(String username) { userRepository.updateStateByUsername(username, User.State.DISABLED); } + /** + * 启用账号 + */ public void enable(String username) { userRepository.updateStateByUsername(username, User.State.NORMAL); } @@ -86,17 +128,26 @@ public class UserService { .collect(UserListItem::new); } - private String encryptPassword(String password) { - String salt = "tY2gNdkt7x%%HcCAFc"; - return SecureUtil.sha256(StrUtil.format("{}{}", salt, password)); + public void changePassword(String oldPassword, String newPassword) { + long id = StpUtil.getLoginIdAsLong(); + User user = userRepository.findById(id).orElseThrow(LoginNotFoundException::new); + if (StrUtil.equals(encryptPassword(oldPassword), user.getPassword())) { + user.setPassword(encryptPassword(newPassword)); + userRepository.save(user); + } else { + throw new ChangePasswordFailureException(); + } } - private User toUser(UserController.RegisterRequest request) { - User user = new User(); - user.setUsername(request.getUsername()); - user.setPassword(encryptPassword(request.getPassword())); - user.setRole(request.getRole()); - return user; + public UserDetail detail(String username) { + User user = userRepository.findByUsername(username).orElseThrow(UserNotFoundException::new); + return new UserDetail(user); + } + + public static class UserNotFoundException extends RuntimeException { + public UserNotFoundException() { + super("账号不存在"); + } } public static class LoginFailureException extends RuntimeException { @@ -133,6 +184,12 @@ public class UserService { } } + public static class ChangePasswordFailureException extends RuntimeException { + public ChangePasswordFailureException() { + super("原密码不正确"); + } + } + @Data public static class UserInformation { private String username; @@ -164,4 +221,23 @@ public class UserService { this.updateTime = user.getUpdateTime(); } } + + @Data + public static class UserDetail { + private Long id; + private String username; + private User.Role role; + private User.State state; + private LocalDateTime createTime; + private LocalDateTime updateTime; + + public UserDetail(User user) { + this.id = user.getId(); + this.username = user.getUsername(); + this.role = user.getRole(); + this.state = user.getState(); + this.createTime = user.getCreateTime(); + this.updateTime = user.getUpdateTime(); + } + } } diff --git a/gringotts-web/src/main/resources/static/assets/component/common.js b/gringotts-web/src/main/resources/static/assets/component/common.js index 01b7c35..28a8a36 100644 --- a/gringotts-web/src/main/resources/static/assets/component/common.js +++ b/gringotts-web/src/main/resources/static/assets/component/common.js @@ -1,4 +1,4 @@ -function crudCommonOptions() { +export function crudCommonOptions() { return { affixHeader: false, stopAutoRefreshWhenModalIsOpen: true, @@ -8,7 +8,7 @@ function crudCommonOptions() { } } -function readOnlyDialogOptions() { +export function readOnlyDialogOptions() { return { actions: [], showCloseButton: false, @@ -18,7 +18,17 @@ function readOnlyDialogOptions() { } } -function paginationCommonOptions(perPage = true, maxButtons = 5) { +export function horizontalFormOptions() { + return { + mode: 'horizontal', + canAccessSuperData: false, + horizontal: { + left: 2, + }, + } +} + +export function paginationCommonOptions(perPage = true, maxButtons = 5) { let option = { type: 'pagination', layout: [ @@ -34,7 +44,7 @@ function paginationCommonOptions(perPage = true, maxButtons = 5) { return option } -function paginationTemplate(perPage = 20, maxButtons = 5) { +export function paginationTemplate(perPage = 20, maxButtons = 5) { return { perPage: perPage, headerToolbar: [ @@ -48,7 +58,7 @@ function paginationTemplate(perPage = 20, maxButtons = 5) { } } -function mappingField(field, mapping) { +export function mappingField(field, mapping) { let mapData = { '*': `\${${field}}`, } @@ -70,97 +80,33 @@ function mappingItem(label, value, color = 'bg-info') { } } -const userRoleMapping = [ +export const userRoleMapping = [ mappingItem('数据提供方', 'PROVIDER', 'bg-blue-500'), mappingItem('数据使用方', 'CUSTOMER', 'bg-purple-500'), mappingItem('审核监管方', 'CHECKER', 'bg-cyan-500'), mappingItem('系统管理员', 'ADMINISTRATOR', 'bg-green-500'), ] -const userStateMapping = [ +export const userStateMapping = [ mappingItem('审查中', 'CHECKING', 'bg-warning'), mappingItem('正常', 'NORMAL', 'bg-success'), mappingItem('禁用', 'DISABLED', 'bg-danger'), ] -function userAddDialog() { +function api(method, url) { return { - actionType: 'dialog', - dialog: { - title: '新用户注册', - actions: [ - { - type: 'reset', - label: '清空', - }, - { - type: 'submit', - label: '注册', - level: 'primary', - } - ], - body: { - type: 'form', - api: '${base}/user/register', - mode: 'horizontal', - canAccessSuperData: false, - horizontal: { - left: 2, - }, - body: [ - { - type: 'input-email', - name: 'username', - label: '邮箱', - placeholder: '请输入邮箱', - required: true, - clearable: true, - clearValueOnEmpty: true, - validateApi: '${base}/user/exists_username/${username}' - }, - { - type: 'input-password', - name: 'password', - label: '密码', - placeholder: '请输入密码', - required: true, - clearable: true, - clearValueOnEmpty: true, - validations: { - matchRegexp: /^(?=.*\d)(?!.*(\d)\1{2})(?!.*(012|123|234|345|456|567|678|789|987|876|765|654|543|432|321|210))(?=.*[a-zA-Z])(?=.*[^\da-zA-Z\s]).{8,16}$/ - }, - validationErrors: { - matchRegexp: '密码至少包含字母、数字、特殊字符,8-16位,并且不能连续出现3个大小连续或相同的数字', - } - }, - { - type: 'input-password', - name: 'confirm-password', - label: '确认密码', - placeholder: '请再次输入密码', - required: true, - clearable: true, - validations: { - equalsField: 'password' - }, - validationErrors: { - equalsField: '两次输入密码不一致', - } - }, - { - type: 'radios', - name: 'role', - label: '角色', - required: true, - selectFirst: true, - options: [ - {label: '数据提供方', value: 'PROVIDER'}, - {label: '数据使用方', value: 'CUSTOMER'}, - {label: '审查监管方', value: 'CHECKER'}, - ] - }, - ] - } + method: method, + url: url, + headers: { + token: '${token|default:undefined}', } } } + +export function apiGet(url) { + return api('get', url) +} + +export function apiPost(url) { + return api('post', url) +} diff --git a/gringotts-web/src/main/resources/static/assets/component/constants.js b/gringotts-web/src/main/resources/static/assets/component/constants.js index 20941f3..ff8bbfa 100644 --- a/gringotts-web/src/main/resources/static/assets/component/constants.js +++ b/gringotts-web/src/main/resources/static/assets/component/constants.js @@ -1,5 +1,6 @@ -const information = { - debug: false, - baseUrl: 'http://127.0.0.1:20080', +export const information = { + debug: true, + baseUrl: '', + // baseUrl: 'http://127.0.0.1:20080', title: '可信供给中心', } diff --git a/gringotts-web/src/main/resources/static/assets/component/pages/overview-tab.js b/gringotts-web/src/main/resources/static/assets/component/pages/tab-overview.js similarity index 79% rename from gringotts-web/src/main/resources/static/assets/component/pages/overview-tab.js rename to gringotts-web/src/main/resources/static/assets/component/pages/tab-overview.js index b6d6d74..a149562 100644 --- a/gringotts-web/src/main/resources/static/assets/component/pages/overview-tab.js +++ b/gringotts-web/src/main/resources/static/assets/component/pages/tab-overview.js @@ -1,4 +1,4 @@ -function overviewTab() { +export function tabOverview() { return { title: '概览', icon: 'fa fa-house', diff --git a/gringotts-web/src/main/resources/static/assets/component/pages/user-tab.js b/gringotts-web/src/main/resources/static/assets/component/pages/tab-user.js similarity index 89% rename from gringotts-web/src/main/resources/static/assets/component/pages/user-tab.js rename to gringotts-web/src/main/resources/static/assets/component/pages/tab-user.js index 6ea0efd..5831393 100644 --- a/gringotts-web/src/main/resources/static/assets/component/pages/user-tab.js +++ b/gringotts-web/src/main/resources/static/assets/component/pages/tab-user.js @@ -1,14 +1,14 @@ -function userTab() { +import {userRegisterDialog} from './user/dialog-user-register.js' +import {apiGet, crudCommonOptions, mappingField, userRoleMapping, userStateMapping} from '../common.js' + +export function tabUser() { return { title: '用户管理', icon: 'fa fa-user', body: [ { type: 'crud', - api: { - method: 'get', - url: '${base}/user_management/list' - }, + api: apiGet('${base}/user_management/list'), ...crudCommonOptions(), headerToolbar: [ 'reload', @@ -16,8 +16,8 @@ function userTab() { type: 'action', icon: 'fa fa-plus', tooltip: '新增账号', - ...userAddDialog(), - } + ...userRegisterDialog(), + }, ], columns: [ { @@ -61,7 +61,7 @@ function userTab() { size: 'xs', }, { - visibleOn: "${state === 'NORMAL'}", + visibleOn: "${state === 'NORMAL' && role !== 'ADMINISTRATOR'}", label: '禁用', icon: 'fa fa-ban', level: 'danger', @@ -73,7 +73,7 @@ function userTab() { }, { - visibleOn: "${state === 'DISABLED'}", + visibleOn: "${state === 'DISABLED' && role !== 'ADMINISTRATOR'}", label: '启用', icon: 'fa fa-check', level: 'success', diff --git a/gringotts-web/src/main/resources/static/assets/component/pages/user/dialog-user-change-password.js b/gringotts-web/src/main/resources/static/assets/component/pages/user/dialog-user-change-password.js new file mode 100644 index 0000000..950cec4 --- /dev/null +++ b/gringotts-web/src/main/resources/static/assets/component/pages/user/dialog-user-change-password.js @@ -0,0 +1,64 @@ +import {horizontalFormOptions, apiPost} from '../../common.js' + +export function userChangePasswordDialog() { + return { + actionType: 'dialog', + dialog: { + title: '修改密码', + actions: [ + { + type: 'reset', + label: '清空', + }, + { + type: 'submit', + label: '修改', + level: 'primary', + } + ], + body: { + type: 'form', + api: apiPost('${base}/user_management/change_password'), + ...horizontalFormOptions(), + body: [ + { + type: 'input-password', + name: 'oldPassword', + label: '旧密码', + placeholder: '请输入旧密码', + required: true, + }, + { + type: 'input-password', + name: 'newPassword', + label: '新密码', + placeholder: '请输入新密码', + required: true, + clearable: true, + clearValueOnEmpty: true, + validations: { + matchRegexp: /^(?=.*\d)(?!.*(\d)\1{2})(?!.*(012|123|234|345|456|567|678|789|987|876|765|654|543|432|321|210))(?=.*[a-zA-Z])(?=.*[^\da-zA-Z\s]).{8,16}$/ + }, + validationErrors: { + matchRegexp: '密码至少包含字母、数字、特殊字符,8-16位,并且不能连续出现3个大小连续或相同的数字', + } + }, + { + type: 'input-password', + name: 'confirmNewPassword', + label: '确认新密码', + placeholder: '请再次输入新密码', + required: true, + clearable: true, + validations: { + equalsField: 'newPassword' + }, + validationErrors: { + equalsField: '两次输入密码不一致', + } + }, + ] + } + } + } +} \ No newline at end of file diff --git a/gringotts-web/src/main/resources/static/assets/component/pages/user/dialog-user-detail.js b/gringotts-web/src/main/resources/static/assets/component/pages/user/dialog-user-detail.js new file mode 100644 index 0000000..246ca27 --- /dev/null +++ b/gringotts-web/src/main/resources/static/assets/component/pages/user/dialog-user-detail.js @@ -0,0 +1,34 @@ +import {horizontalFormOptions} from "../../common.js"; + +export function userDetailDialog() { + return { + actionType: 'dialog', + dialog: { + title: '用户注册', + actions: [], + body: { + type: 'form', + api: '${base}/user_management/detail/${username}', + ...horizontalFormOptions(), + static: true, + body: [ + { + type: 'input-email', + name: 'username', + label: '邮箱', + }, + { + type: 'input-password', + name: 'password', + label: '密码', + }, + { + type: 'radios', + name: 'role', + label: '角色', + }, + ] + } + } + } +} \ No newline at end of file diff --git a/gringotts-web/src/main/resources/static/assets/component/pages/user/dialog-user-register.js b/gringotts-web/src/main/resources/static/assets/component/pages/user/dialog-user-register.js new file mode 100644 index 0000000..40f11cf --- /dev/null +++ b/gringotts-web/src/main/resources/static/assets/component/pages/user/dialog-user-register.js @@ -0,0 +1,79 @@ +import {horizontalFormOptions} from "../../common.js"; + +export function userRegisterDialog() { + return { + actionType: 'dialog', + dialog: { + title: '用户注册', + actions: [ + { + type: 'reset', + label: '清空', + }, + { + type: 'submit', + label: '注册', + level: 'primary', + } + ], + body: { + type: 'form', + api: '${base}/user/register', + ...horizontalFormOptions(), + body: [ + { + type: 'input-email', + name: 'username', + label: '邮箱', + placeholder: '请输入邮箱', + required: true, + clearable: true, + clearValueOnEmpty: true, + validateApi: '${base}/user/exists_username/${username}' + }, + { + type: 'input-password', + name: 'password', + label: '密码', + placeholder: '请输入密码', + required: true, + clearable: true, + clearValueOnEmpty: true, + validations: { + matchRegexp: /^(?=.*\d)(?!.*(\d)\1{2})(?!.*(012|123|234|345|456|567|678|789|987|876|765|654|543|432|321|210))(?=.*[a-zA-Z])(?=.*[^\da-zA-Z\s]).{8,16}$/ + }, + validationErrors: { + matchRegexp: '密码至少包含字母、数字、特殊字符,8-16位,并且不能连续出现3个大小连续或相同的数字', + } + }, + { + type: 'input-password', + name: 'confirm-password', + label: '确认密码', + placeholder: '请再次输入密码', + required: true, + clearable: true, + validations: { + equalsField: 'password' + }, + validationErrors: { + equalsField: '两次输入密码不一致', + } + }, + { + type: 'radios', + name: 'role', + label: '角色', + required: true, + selectFirst: true, + options: [ + {label: '数据提供方', value: 'PROVIDER'}, + {label: '数据使用方', value: 'CUSTOMER'}, + {label: '审查监管方', value: 'CHECKER'}, + ] + }, + ] + } + } + } +} \ No newline at end of file diff --git a/gringotts-web/src/main/resources/static/index.html b/gringotts-web/src/main/resources/static/index.html index 70cae04..a7f7c8d 100644 --- a/gringotts-web/src/main/resources/static/index.html +++ b/gringotts-web/src/main/resources/static/index.html @@ -23,23 +23,20 @@
- - - - - - -