Dev

Co-authored-by: John Niang <johnniang@riseup.net>
pull/286/head
Ryan Wang 2019-06-19 14:53:36 +08:00 committed by GitHub
commit d4f3850cb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 420 additions and 76 deletions

View File

@ -41,7 +41,7 @@ wget https://github.com/halo-dev/halo/releases/download/v1.0.2/halo-1.0.2.jar -O
nohup java -jar halo-latest.jar >/dev/null 2>&1& nohup java -jar halo-latest.jar >/dev/null 2>&1&
``` ```
详细文档请移步:<https://halo.run/docs> 详细文档请移步:<https://halo.run/guide>
## 博客示例 ## 博客示例

View File

@ -196,6 +196,8 @@ public class InstallController {
installParam.update(user); installParam.update(user);
// Set password manually // Set password manually
userService.setPassword(user, installParam.getPassword()); userService.setPassword(user, installParam.getPassword());
// Set default avatar
userService.setDefaultAvatar(user);
// Update user // Update user
return userService.update(user); return userService.update(user);
}).orElseGet(() -> userService.createBy(installParam)); }).orElseGet(() -> userService.createBy(installParam));

View File

@ -0,0 +1,34 @@
package run.halo.app.controller.admin.api;
import io.swagger.annotations.ApiOperation;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import run.halo.app.service.TraceService;
import java.util.List;
/**
* Trace controller.
*
* @author johnniang
* @date 19-6-18
*/
@RestController
@RequestMapping("/api/admin/traces")
public class TraceController {
private final TraceService traceService;
public TraceController(TraceService traceService) {
this.traceService = traceService;
}
@GetMapping
@ApiOperation("Lists http traces")
public List<HttpTrace> listHttpTraces() {
return traceService.listHttpTraces();
}
}

View File

@ -55,7 +55,7 @@ public class OptionController {
@ApiOperation("Options for comment") @ApiOperation("Options for comment")
public Map<String, Object> comment() { public Map<String, Object> comment() {
List<String> keys = new ArrayList<>(); List<String> keys = new ArrayList<>();
keys.add("comment_gavatar_default"); keys.add("comment_gravatar_default");
keys.add("comment_content_placeholder"); keys.add("comment_content_placeholder");
return optionService.listOptions(keys); return optionService.listOptions(keys);
} }

View File

@ -1,14 +1,28 @@
package run.halo.app.controller.core; package run.halo.app.controller.core;
import cn.hutool.core.text.StrBuilder;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.servlet.error.ErrorController; import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.util.NestedServletException;
import run.halo.app.exception.HaloException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.service.ThemeService; import run.halo.app.service.ThemeService;
import run.halo.app.utils.FilenameUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.Map;
/** /**
* Error page Controller * Error page Controller
@ -18,18 +32,30 @@ import javax.servlet.http.HttpServletRequest;
*/ */
@Slf4j @Slf4j
@Controller @Controller
public class CommonController implements ErrorController { @RequestMapping("${server.error.path:${error.path:/error}}")
public class CommonController extends AbstractErrorController {
private static final String ERROR_PATH = "/error"; private static final String NOT_FOUND_TEMPLATE = "404.ftl";
private static final String NOT_FROUND_TEMPLATE = "404.ftl";
private static final String INTERNAL_ERROR_TEMPLATE = "500.ftl"; private static final String INTERNAL_ERROR_TEMPLATE = "500.ftl";
private static final String ERROR_TEMPLATE = "error.ftl";
private static final String DEFAULT_ERROR_PATH = "common/error/error";
private final ThemeService themeService; private final ThemeService themeService;
public CommonController(ThemeService themeService) { private final ErrorProperties errorProperties;
private final ErrorAttributes errorAttributes;
public CommonController(ThemeService themeService,
ErrorAttributes errorAttributes,
ServerProperties serverProperties) {
super(errorAttributes);
this.themeService = themeService; this.themeService = themeService;
this.errorAttributes = errorAttributes;
this.errorProperties = serverProperties.getError();
} }
/** /**
@ -38,26 +64,26 @@ public class CommonController implements ErrorController {
* @param request request * @param request request
* @return String * @return String
*/ */
@GetMapping(value = ERROR_PATH) @GetMapping
public String handleError(HttpServletRequest request) { public String handleError(HttpServletRequest request, HttpServletResponse response, Model model) {
final Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); HttpStatus status = getStatus(request);
log.error("Error path: [{}], status: [{}]", getErrorPath(), statusCode); log.error("Error path: [{}], status: [{}]", getErrorPath(), status);
// Get the exception handleCustomException(request);
Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
if (throwable != null) { Map<String, Object> errorDetail = Collections.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request)));
log.error("Captured an exception", throwable); model.addAttribute("error", errorDetail);
if (StringUtils.startsWithIgnoreCase(throwable.getMessage(), "Could not resolve view with name '")) { log.debug("Error detail: [{}]", errorDetail);
// TODO May cause unknown-reason problem
// if Ftl was not found then redirect to /404 if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
return contentNotFround(); return contentInternalError();
} } else if (status.equals(HttpStatus.NOT_FOUND)) {
return contentNotFound();
} else {
return defaultErrorHandler();
} }
return statusCode == 500 ? contentInternalError() : contentNotFround();
} }
/** /**
@ -66,14 +92,16 @@ public class CommonController implements ErrorController {
* @return String * @return String
*/ */
@GetMapping(value = "/404") @GetMapping(value = "/404")
public String contentNotFround() { public String contentNotFound() {
if (!themeService.templateExists(NOT_FROUND_TEMPLATE)) { if (themeService.templateExists(ERROR_TEMPLATE)) {
return "common/error/404"; return getActualTemplatePath(ERROR_TEMPLATE);
} }
StrBuilder path = new StrBuilder("themes/");
path.append(themeService.getActivatedTheme().getFolderName()); if (themeService.templateExists(NOT_FOUND_TEMPLATE)) {
path.append("/404"); return getActualTemplatePath(NOT_FOUND_TEMPLATE);
return path.toString(); }
return defaultErrorHandler();
} }
/** /**
@ -83,15 +111,67 @@ public class CommonController implements ErrorController {
*/ */
@GetMapping(value = "/500") @GetMapping(value = "/500")
public String contentInternalError() { public String contentInternalError() {
if (!themeService.templateExists(INTERNAL_ERROR_TEMPLATE)) { if (themeService.templateExists(ERROR_TEMPLATE)) {
return "common/error/500"; return getActualTemplatePath(ERROR_TEMPLATE);
} }
StrBuilder path = new StrBuilder("themes/");
path.append(themeService.getActivatedTheme().getFolderName()); if (themeService.templateExists(INTERNAL_ERROR_TEMPLATE)) {
path.append("/500"); return getActualTemplatePath(INTERNAL_ERROR_TEMPLATE);
}
return defaultErrorHandler();
}
private String defaultErrorHandler() {
return DEFAULT_ERROR_PATH;
}
private String getActualTemplatePath(@NonNull String template) {
Assert.hasText(template, "FTL template must not be blank");
StringBuilder path = new StringBuilder();
path.append("themes/")
.append(themeService.getActivatedTheme().getFolderName())
.append('/')
.append(FilenameUtils.getBasename(template));
return path.toString(); return path.toString();
} }
/**
* Handles custom exception, like HaloException.
*
* @param request http servlet request must not be null
*/
private void handleCustomException(@NonNull HttpServletRequest request) {
Assert.notNull(request, "Http servlet request must not be null");
Object throwableObject = request.getAttribute("javax.servlet.error.exception");
if (throwableObject == null) {
return;
}
Throwable throwable = (Throwable) throwableObject;
log.error("Captured an exception", throwable);
if (throwable instanceof NestedServletException) {
Throwable rootCause = ((NestedServletException) throwable).getRootCause();
if (rootCause instanceof HaloException) {
HaloException haloException = (HaloException) rootCause;
request.setAttribute("javax.servlet.error.status_code", haloException.getStatus().value());
request.setAttribute("javax.servlet.error.exception", rootCause);
request.setAttribute("javax.servlet.error.message", haloException.getMessage());
}
} else if (StringUtils.startsWithIgnoreCase(throwable.getMessage(), "Could not resolve view with name '")) {
request.setAttribute("javax.servlet.error.status_code", HttpStatus.NOT_FOUND.value());
NotFoundException viewNotFound = new NotFoundException("该路径没有对应的模板");
request.setAttribute("javax.servlet.error.exception", viewNotFound);
request.setAttribute("javax.servlet.error.message", viewNotFound.getMessage());
}
}
/** /**
* Returns the path of the error page. * Returns the path of the error page.
* *
@ -99,6 +179,23 @@ public class CommonController implements ErrorController {
*/ */
@Override @Override
public String getErrorPath() { public String getErrorPath() {
return ERROR_PATH; return this.errorProperties.getPath();
}
/**
* Determine if the stacktrace attribute should be included.
*
* @param request the source request
* @return if the stacktrace attribute should be included
*/
protected boolean isIncludeStackTrace(HttpServletRequest request) {
ErrorProperties.IncludeStacktrace include = errorProperties.getIncludeStacktrace();
if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
return true;
}
if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
return getTraceParameter(request);
}
return false;
} }
} }

View File

@ -29,7 +29,7 @@ public class BaseCommentDTO implements OutputConverter<BaseCommentDTO, BaseComme
private String authorUrl; private String authorUrl;
private String gavatarMd5; private String gravatarMd5;
private String content; private String content;

View File

@ -0,0 +1,20 @@
package run.halo.app.model.dto;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.Date;
/**
* Http trace dto.
*
* @author johnniang
* @date 19-6-18
*/
@Data
@ToString
@EqualsAndHashCode
public class HttpTraceDTO {
}

View File

@ -50,10 +50,10 @@ public class BaseComment extends BaseEntity {
private String authorUrl; private String authorUrl;
/** /**
* Gavatar md5 * Gravatar md5
*/ */
@Column(name = "gavatar_md5", columnDefinition = "varchar(128) default ''") @Column(name = "gravatar_md5", columnDefinition = "varchar(128) default ''")
private String gavatarMd5; private String gravatarMd5;
/** /**
* Comment content. * Comment content.
@ -117,8 +117,8 @@ public class BaseComment extends BaseEntity {
authorUrl = ""; authorUrl = "";
} }
if (gavatarMd5 == null) { if (gravatarMd5 == null) {
gavatarMd5 = ""; gravatarMd5 = "";
} }
if (status == null) { if (status == null) {

View File

@ -62,13 +62,9 @@ public class PostParam implements InputConverter<Post> {
@Override @Override
public Post convertTo() { public Post convertTo() {
if (StringUtils.isBlank(url)) { if (StringUtils.isBlank(url)) {
url = HaloUtils.normalizeUrl(title); url = title;
} else {
url = HaloUtils.normalizeUrl(url);
} }
url = HaloUtils.initializeUrlIfBlank(url);
Post post = InputConverter.super.convertTo(); Post post = InputConverter.super.convertTo();
// Crypt password // Crypt password
if (StringUtils.isNotBlank(password)) { if (StringUtils.isNotBlank(password)) {
@ -81,13 +77,9 @@ public class PostParam implements InputConverter<Post> {
@Override @Override
public void update(Post post) { public void update(Post post) {
if (StringUtils.isBlank(url)) { if (StringUtils.isBlank(url)) {
url = HaloUtils.normalizeUrl(title); url = title;
} else {
url = HaloUtils.normalizeUrl(url);
} }
url = HaloUtils.initializeUrlIfBlank(url);
InputConverter.super.update(post); InputConverter.super.update(post);
// Crypt password // Crypt password

View File

@ -9,7 +9,7 @@ package run.halo.app.model.properties;
*/ */
public enum CommentProperties implements PropertyEnum { public enum CommentProperties implements PropertyEnum {
GAVATAR_DEFAULT("comment_gavatar_default", String.class, "mm"), GRAVATAR_DEFAULT("comment_gravatar_default", String.class, "mm"),
NEW_NEED_CHECK("comment_new_need_check", Boolean.class, "true"), NEW_NEED_CHECK("comment_new_need_check", Boolean.class, "true"),

View File

@ -0,0 +1,24 @@
package run.halo.app.service;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.lang.NonNull;
import java.util.List;
/**
* Trace service interface.
*
* @author johnniang
* @date 19-6-18
*/
public interface TraceService {
/**
* Gets all http traces.
*
* @return
*/
@NonNull
List<HttpTrace> listHttpTraces();
}

View File

@ -122,4 +122,11 @@ public interface UserService extends CrudService<User, Integer> {
* @param plainPassword plain password must not be blank * @param plainPassword plain password must not be blank
*/ */
void setPassword(@NonNull User user, @NonNull String plainPassword); void setPassword(@NonNull User user, @NonNull String plainPassword);
/**
* Set user default avataruse Gravatar(http://cn.gravatar.com)
*
* @param user user must not be null
*/
void setDefaultAvatar(@NonNull User user);
} }

View File

@ -241,8 +241,8 @@ public abstract class BaseCommentServiceImpl<COMMENT extends BaseComment> extend
comment.setUserAgent(ServletUtils.getHeaderIgnoreCase(HttpHeaders.USER_AGENT)); comment.setUserAgent(ServletUtils.getHeaderIgnoreCase(HttpHeaders.USER_AGENT));
} }
if (comment.getGavatarMd5() == null) { if (comment.getGravatarMd5() == null) {
comment.setGavatarMd5(DigestUtils.md5Hex(comment.getEmail())); comment.setGravatarMd5(DigestUtils.md5Hex(comment.getEmail()));
} }
if (StringUtils.isNotEmpty(comment.getAuthorUrl())) { if (StringUtils.isNotEmpty(comment.getAuthorUrl())) {

View File

@ -346,7 +346,7 @@ public class RecoveryServiceImpl implements RecoveryService {
baseComment.setEmail(commentMap.getOrDefault("commentAuthorEmail", "").toString()); baseComment.setEmail(commentMap.getOrDefault("commentAuthorEmail", "").toString());
baseComment.setIpAddress(commentMap.getOrDefault("commentAuthorIp", "").toString()); baseComment.setIpAddress(commentMap.getOrDefault("commentAuthorIp", "").toString());
baseComment.setAuthorUrl(commentMap.getOrDefault("commentAuthorUrl", "").toString()); baseComment.setAuthorUrl(commentMap.getOrDefault("commentAuthorUrl", "").toString());
baseComment.setGavatarMd5(commentMap.getOrDefault("commentAuthorAvatarMd5", "").toString()); baseComment.setGravatarMd5(commentMap.getOrDefault("commentAuthorAvatarMd5", "").toString());
baseComment.setContent(commentMap.getOrDefault("commentContent", "").toString()); baseComment.setContent(commentMap.getOrDefault("commentContent", "").toString());
baseComment.setUserAgent(commentMap.getOrDefault("commentAgent", "").toString()); baseComment.setUserAgent(commentMap.getOrDefault("commentAgent", "").toString());
baseComment.setIsAdmin(getBooleanOrDefault(commentMap.getOrDefault("isAdmin", "").toString(), false)); baseComment.setIsAdmin(getBooleanOrDefault(commentMap.getOrDefault("isAdmin", "").toString(), false));

View File

@ -0,0 +1,31 @@
package run.halo.app.service.impl;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.boot.actuate.trace.http.HttpTraceRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import run.halo.app.service.TraceService;
import java.util.List;
/**
* @author johnniang
* @date 19-6-18
*/
@Service
public class TraceServiceImpl implements TraceService {
private final HttpTraceRepository httpTraceRepository;
public TraceServiceImpl(HttpTraceRepository httpTraceRepository) {
this.httpTraceRepository = httpTraceRepository;
}
@Override
public List<HttpTrace> listHttpTraces() {
return httpTraceRepository.findAll();
}
}

View File

@ -1,5 +1,7 @@
package run.halo.app.service.impl; package run.halo.app.service.impl;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.BCrypt; import cn.hutool.crypto.digest.BCrypt;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@ -193,4 +195,12 @@ public class UserServiceImpl extends AbstractCrudService<User, Integer> implemen
user.setPassword(BCrypt.hashpw(plainPassword, BCrypt.gensalt())); user.setPassword(BCrypt.hashpw(plainPassword, BCrypt.gensalt()));
} }
@Override
public void setDefaultAvatar(User user) {
Assert.notNull(user, "User must not be null");
StrBuilder gravatar = new StrBuilder("//cn.gravatar.com/avatar/");
gravatar.append(SecureUtil.md5(user.getEmail()));
gravatar.append("?s=256&d=mm");
user.setAvatar(gravatar.toString());
}
} }

View File

@ -66,25 +66,16 @@ public class FileUtils {
public static void deleteFolder(@NonNull Path deletingPath) throws IOException { public static void deleteFolder(@NonNull Path deletingPath) throws IOException {
Assert.notNull(deletingPath, "Deleting path must not be null"); Assert.notNull(deletingPath, "Deleting path must not be null");
if (Files.notExists(deletingPath)) {
return;
}
log.info("Deleting [{}]", deletingPath); log.info("Deleting [{}]", deletingPath);
// Delete folder recursively // Delete folder recursively
org.eclipse.jgit.util.FileUtils.delete(deletingPath.toFile(), org.eclipse.jgit.util.FileUtils.delete(deletingPath.toFile(),
org.eclipse.jgit.util.FileUtils.RECURSIVE | org.eclipse.jgit.util.FileUtils.RETRY); org.eclipse.jgit.util.FileUtils.RECURSIVE | org.eclipse.jgit.util.FileUtils.RETRY);
// try (Stream<Path> pathStream = Files.walk(deletingPath)) {
// pathStream.sorted(Comparator.reverseOrder())
// .peek(path -> log.debug("Try to delete [{}]", path.toString()))
// .forEach(path -> {
// try {
// Files.delete(path);
// log.debug("Deleted [{}] successfully", path.toString());
// } catch (IOException e) {
// throw new ServiceException("Failed to delete " + path.toString(), e).setErrorData(deletingPath.toString());
// }
// });
// }
log.info("Deleted [{}] successfully", deletingPath); log.info("Deleted [{}] successfully", deletingPath);
} }

View File

@ -1 +1 @@
<!DOCTYPE html><html lang=zh-cmn-Hans><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><meta name=robots content=noindex,nofllow><meta name=generator content=Halo><link rel=icon href=/logo.png><title>Halo Dashboard</title><link href=/css/chunk-0337f7a6.4c6b622f.css rel=prefetch><link href=/css/chunk-1079f749.94b473ad.css rel=prefetch><link href=/css/chunk-14e0b302.32f796a8.css rel=prefetch><link href=/css/chunk-161dc990.5ac5144c.css rel=prefetch><link href=/css/chunk-1be69b35.43c1fc12.css rel=prefetch><link href=/css/chunk-31c8ea42.4a090118.css rel=prefetch><link href=/css/chunk-5000e55c.7fb9bc61.css rel=prefetch><link href=/css/chunk-6d8b31f6.ad8d17b2.css rel=prefetch><link href=/css/chunk-81d936d8.05888d95.css rel=prefetch><link href=/css/chunk-b2d0b040.389eca76.css rel=prefetch><link href=/css/chunk-bb4f0d4a.c1990d7c.css rel=prefetch><link href=/css/chunk-bfd5bbcc.6a83ae7d.css rel=prefetch><link href=/css/chunk-c0a1d3c4.09186be6.css rel=prefetch><link href=/css/chunk-cec31564.6f053d75.css rel=prefetch><link href=/css/fail.809a6bc5.css rel=prefetch><link href=/js/chunk-0337f7a6.11326d77.js rel=prefetch><link href=/js/chunk-0ba750a2.b786c9db.js rel=prefetch><link href=/js/chunk-1079f749.ec67c7db.js rel=prefetch><link href=/js/chunk-142c8832.0f8270b3.js rel=prefetch><link href=/js/chunk-14e0b302.a86d1254.js rel=prefetch><link href=/js/chunk-161dc990.5de9313f.js rel=prefetch><link href=/js/chunk-1be69b35.81559bfc.js rel=prefetch><link href=/js/chunk-2d0b64bf.61d5d7c3.js rel=prefetch><link href=/js/chunk-2d0d65a2.2249765a.js rel=prefetch><link href=/js/chunk-2d21a35c.eda7a11a.js rel=prefetch><link href=/js/chunk-2d228d13.85b46532.js rel=prefetch><link href=/js/chunk-31c8ea42.0b2feab9.js rel=prefetch><link href=/js/chunk-5000e55c.3bd9ce3a.js rel=prefetch><link href=/js/chunk-5bf599cc.ac9398f3.js rel=prefetch><link href=/js/chunk-6d8b31f6.b64e5366.js rel=prefetch><link href=/js/chunk-81d936d8.5c1d2539.js rel=prefetch><link href=/js/chunk-87e2df70.0ada7d4e.js rel=prefetch><link href=/js/chunk-b2d0b040.b0d70d07.js rel=prefetch><link href=/js/chunk-bb4f0d4a.048dc3a4.js rel=prefetch><link href=/js/chunk-bfd5bbcc.d2ca1e80.js rel=prefetch><link href=/js/chunk-c0a1d3c4.41d0d3f8.js rel=prefetch><link href=/js/chunk-cec31564.cfe3fd85.js rel=prefetch><link href=/js/fail.61f30b0f.js rel=prefetch><link href=/css/app.f8b02c30.css rel=preload as=style><link href=/css/chunk-vendors.ee4e8dbf.css rel=preload as=style><link href=/js/app.845922f6.js rel=preload as=script><link href=/js/chunk-vendors.2f7bce79.js rel=preload as=script><link href=/css/chunk-vendors.ee4e8dbf.css rel=stylesheet><link href=/css/app.f8b02c30.css rel=stylesheet></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.2f7bce79.js></script><script src=/js/app.845922f6.js></script></body></html> <!DOCTYPE html><html lang=zh-cmn-Hans><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge"><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><meta name=robots content=noindex,nofllow><meta name=generator content=Halo><link rel=icon href=/logo.png><title>Halo Dashboard</title><link href=/css/chunk-0337f7a6.4c6b622f.css rel=prefetch><link href=/css/chunk-1079f749.94b473ad.css rel=prefetch><link href=/css/chunk-14e0b302.32f796a8.css rel=prefetch><link href=/css/chunk-161dc990.5ac5144c.css rel=prefetch><link href=/css/chunk-1be69b35.43c1fc12.css rel=prefetch><link href=/css/chunk-31c8ea42.4a090118.css rel=prefetch><link href=/css/chunk-5000e55c.7fb9bc61.css rel=prefetch><link href=/css/chunk-6d8b31f6.ad8d17b2.css rel=prefetch><link href=/css/chunk-81d936d8.05888d95.css rel=prefetch><link href=/css/chunk-b2d0b040.389eca76.css rel=prefetch><link href=/css/chunk-bb4f0d4a.c1990d7c.css rel=prefetch><link href=/css/chunk-bfd5bbcc.6a83ae7d.css rel=prefetch><link href=/css/chunk-c0a1d3c4.09186be6.css rel=prefetch><link href=/css/chunk-cec31564.6f053d75.css rel=prefetch><link href=/css/fail.809a6bc5.css rel=prefetch><link href=/js/chunk-0337f7a6.11326d77.js rel=prefetch><link href=/js/chunk-0ba750a2.b786c9db.js rel=prefetch><link href=/js/chunk-1079f749.ec67c7db.js rel=prefetch><link href=/js/chunk-142c8832.0f8270b3.js rel=prefetch><link href=/js/chunk-14e0b302.a86d1254.js rel=prefetch><link href=/js/chunk-161dc990.5de9313f.js rel=prefetch><link href=/js/chunk-1be69b35.81559bfc.js rel=prefetch><link href=/js/chunk-2d0b64bf.61d5d7c3.js rel=prefetch><link href=/js/chunk-2d0d65a2.2249765a.js rel=prefetch><link href=/js/chunk-2d21a35c.eda7a11a.js rel=prefetch><link href=/js/chunk-2d228d13.85b46532.js rel=prefetch><link href=/js/chunk-31c8ea42.0b2feab9.js rel=prefetch><link href=/js/chunk-5000e55c.3bd9ce3a.js rel=prefetch><link href=/js/chunk-5bf599cc.6555f060.js rel=prefetch><link href=/js/chunk-6d8b31f6.b64e5366.js rel=prefetch><link href=/js/chunk-81d936d8.8bec77ac.js rel=prefetch><link href=/js/chunk-87e2df70.0ada7d4e.js rel=prefetch><link href=/js/chunk-b2d0b040.b0d70d07.js rel=prefetch><link href=/js/chunk-bb4f0d4a.ef7d1ded.js rel=prefetch><link href=/js/chunk-bfd5bbcc.d2ca1e80.js rel=prefetch><link href=/js/chunk-c0a1d3c4.41d0d3f8.js rel=prefetch><link href=/js/chunk-cec31564.cfe3fd85.js rel=prefetch><link href=/js/fail.61f30b0f.js rel=prefetch><link href=/css/app.852293da.css rel=preload as=style><link href=/css/chunk-vendors.ee4e8dbf.css rel=preload as=style><link href=/js/app.29b04043.js rel=preload as=script><link href=/js/chunk-vendors.2f7bce79.js rel=preload as=script><link href=/css/chunk-vendors.ee4e8dbf.css rel=stylesheet><link href=/css/app.852293da.css rel=stylesheet></head><body><noscript><strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id=app></div><script src=/js/chunk-vendors.2f7bce79.js></script><script src=/js/app.29b04043.js></script></body></html>

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

View File

@ -0,0 +1,133 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="alternate" type="application/rss+xml" title="atom 1.0" href="${context!}/atom.xml">
<title>${error.status!} | ${error.error!}</title>
<style type="text/css">
body {
padding: 30px 20px;
font-family: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
color: #727272;
line-height: 1.6;
}
.container {
max-width: 500px;
margin: 0 auto;
}
h1 {
margin: 0;
font-size: 60px;
line-height: 1;
color: #252427;
font-weight: 700;
display: inline-block;
}
h2 {
margin: 100px 0 0;
font-weight: 600;
letter-spacing: 0.1em;
color: #A299AC;
text-transform: uppercase;
}
p {
font-size: 16px;
margin: 1em 0;
}
@media screen and (min-width: 768px) {
body {
padding: 50px;
}
}
@media screen and (max-width: 480px) {
h1 {
font-size: 48px;
}
}
.title {
position: relative;
}
.title::before {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
background-color: #000;
transform-origin: bottom right;
transform: scaleX(0);
transition: transform 0.5s ease;
}
.title:hover::before {
transform-origin: bottom left;
transform: scaleX(1);
}
.back-home button {
z-index: 1;
position: relative;
font-size: inherit;
font-family: inherit;
color: white;
padding: 0.5em 1em;
outline: none;
border: none;
background-color: hsl(0, 0%, 0%);
overflow: hidden;
transition: color 0.4s ease-in-out;
}
.back-home button::before {
content: '';
z-index: -1;
position: absolute;
top: 100%;
left: 100%;
width: 1em;
height: 1em;
border-radius: 50%;
background-color: #fff;
transform-origin: center;
transform: translate3d(-50%, -50%, 0) scale3d(0, 0, 0);
transition: transform 0.45s ease-in-out;
}
.back-home button:hover {
cursor: pointer;
color: #000;
}
.back-home button:hover::before {
transform: translate3d(-50%, -50%, 0) scale3d(15, 15, 15);
}
</style>
</head>
<body>
<div class="container">
<h2>${error.status!}</h2>
<h1 class="title">${error.error!}.</h1>
<p>${error.message!}</p>
<div class="back-home">
<button onclick="window.location.href='${context!}'">首页</button>
</div>
</div>
</body>
</html>

View File

@ -52,6 +52,9 @@
</#macro> </#macro>
<#macro globalHeader> <#macro globalHeader>
<#if options.spider_disabled!false>
<meta name="robots" content="none">
</#if>
<meta name="generator" content="Halo ${version!}" /> <meta name="generator" content="Halo ${version!}" />
<@custom_head /> <@custom_head />
<@verification /> <@verification />