mirror of https://github.com/halo-dev/halo
commit
d4f3850cb2
|
@ -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&
|
||||
```
|
||||
|
||||
详细文档请移步:<https://halo.run/docs>
|
||||
详细文档请移步:<https://halo.run/guide>
|
||||
|
||||
## 博客示例
|
||||
|
||||
|
|
|
@ -196,6 +196,8 @@ public class InstallController {
|
|||
installParam.update(user);
|
||||
// Set password manually
|
||||
userService.setPassword(user, installParam.getPassword());
|
||||
// Set default avatar
|
||||
userService.setDefaultAvatar(user);
|
||||
// Update user
|
||||
return userService.update(user);
|
||||
}).orElseGet(() -> userService.createBy(installParam));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -55,7 +55,7 @@ public class OptionController {
|
|||
@ApiOperation("Options for comment")
|
||||
public Map<String, Object> comment() {
|
||||
List<String> keys = new ArrayList<>();
|
||||
keys.add("comment_gavatar_default");
|
||||
keys.add("comment_gravatar_default");
|
||||
keys.add("comment_content_placeholder");
|
||||
return optionService.listOptions(keys);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,28 @@
|
|||
package run.halo.app.controller.core;
|
||||
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
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.ui.Model;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
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.utils.FilenameUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Error page Controller
|
||||
|
@ -18,18 +32,30 @@ import javax.servlet.http.HttpServletRequest;
|
|||
*/
|
||||
@Slf4j
|
||||
@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_FROUND_TEMPLATE = "404.ftl";
|
||||
private static final String NOT_FOUND_TEMPLATE = "404.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;
|
||||
|
||||
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.errorAttributes = errorAttributes;
|
||||
this.errorProperties = serverProperties.getError();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,26 +64,26 @@ public class CommonController implements ErrorController {
|
|||
* @param request request
|
||||
* @return String
|
||||
*/
|
||||
@GetMapping(value = ERROR_PATH)
|
||||
public String handleError(HttpServletRequest request) {
|
||||
final Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
|
||||
@GetMapping
|
||||
public String handleError(HttpServletRequest request, HttpServletResponse response, Model model) {
|
||||
HttpStatus status = getStatus(request);
|
||||
|
||||
log.error("Error path: [{}], status: [{}]", getErrorPath(), statusCode);
|
||||
log.error("Error path: [{}], status: [{}]", getErrorPath(), status);
|
||||
|
||||
// Get the exception
|
||||
Throwable throwable = (Throwable) request.getAttribute("javax.servlet.error.exception");
|
||||
handleCustomException(request);
|
||||
|
||||
if (throwable != null) {
|
||||
log.error("Captured an exception", throwable);
|
||||
Map<String, Object> errorDetail = Collections.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request)));
|
||||
model.addAttribute("error", errorDetail);
|
||||
|
||||
if (StringUtils.startsWithIgnoreCase(throwable.getMessage(), "Could not resolve view with name '")) {
|
||||
// TODO May cause unknown-reason problem
|
||||
// if Ftl was not found then redirect to /404
|
||||
return contentNotFround();
|
||||
}
|
||||
log.debug("Error detail: [{}]", errorDetail);
|
||||
|
||||
if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
|
||||
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
|
||||
*/
|
||||
@GetMapping(value = "/404")
|
||||
public String contentNotFround() {
|
||||
if (!themeService.templateExists(NOT_FROUND_TEMPLATE)) {
|
||||
return "common/error/404";
|
||||
public String contentNotFound() {
|
||||
if (themeService.templateExists(ERROR_TEMPLATE)) {
|
||||
return getActualTemplatePath(ERROR_TEMPLATE);
|
||||
}
|
||||
StrBuilder path = new StrBuilder("themes/");
|
||||
path.append(themeService.getActivatedTheme().getFolderName());
|
||||
path.append("/404");
|
||||
return path.toString();
|
||||
|
||||
if (themeService.templateExists(NOT_FOUND_TEMPLATE)) {
|
||||
return getActualTemplatePath(NOT_FOUND_TEMPLATE);
|
||||
}
|
||||
|
||||
return defaultErrorHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,15 +111,67 @@ public class CommonController implements ErrorController {
|
|||
*/
|
||||
@GetMapping(value = "/500")
|
||||
public String contentInternalError() {
|
||||
if (!themeService.templateExists(INTERNAL_ERROR_TEMPLATE)) {
|
||||
return "common/error/500";
|
||||
if (themeService.templateExists(ERROR_TEMPLATE)) {
|
||||
return getActualTemplatePath(ERROR_TEMPLATE);
|
||||
}
|
||||
StrBuilder path = new StrBuilder("themes/");
|
||||
path.append(themeService.getActivatedTheme().getFolderName());
|
||||
path.append("/500");
|
||||
|
||||
if (themeService.templateExists(INTERNAL_ERROR_TEMPLATE)) {
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -99,6 +179,23 @@ public class CommonController implements ErrorController {
|
|||
*/
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public class BaseCommentDTO implements OutputConverter<BaseCommentDTO, BaseComme
|
|||
|
||||
private String authorUrl;
|
||||
|
||||
private String gavatarMd5;
|
||||
private String gravatarMd5;
|
||||
|
||||
private String content;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
||||
}
|
|
@ -50,10 +50,10 @@ public class BaseComment extends BaseEntity {
|
|||
private String authorUrl;
|
||||
|
||||
/**
|
||||
* Gavatar md5
|
||||
* Gravatar md5
|
||||
*/
|
||||
@Column(name = "gavatar_md5", columnDefinition = "varchar(128) default ''")
|
||||
private String gavatarMd5;
|
||||
@Column(name = "gravatar_md5", columnDefinition = "varchar(128) default ''")
|
||||
private String gravatarMd5;
|
||||
|
||||
/**
|
||||
* Comment content.
|
||||
|
@ -117,8 +117,8 @@ public class BaseComment extends BaseEntity {
|
|||
authorUrl = "";
|
||||
}
|
||||
|
||||
if (gavatarMd5 == null) {
|
||||
gavatarMd5 = "";
|
||||
if (gravatarMd5 == null) {
|
||||
gravatarMd5 = "";
|
||||
}
|
||||
|
||||
if (status == null) {
|
||||
|
|
|
@ -62,13 +62,9 @@ public class PostParam implements InputConverter<Post> {
|
|||
@Override
|
||||
public Post convertTo() {
|
||||
if (StringUtils.isBlank(url)) {
|
||||
url = HaloUtils.normalizeUrl(title);
|
||||
} else {
|
||||
url = HaloUtils.normalizeUrl(url);
|
||||
url = title;
|
||||
}
|
||||
|
||||
url = HaloUtils.initializeUrlIfBlank(url);
|
||||
|
||||
Post post = InputConverter.super.convertTo();
|
||||
// Crypt password
|
||||
if (StringUtils.isNotBlank(password)) {
|
||||
|
@ -81,13 +77,9 @@ public class PostParam implements InputConverter<Post> {
|
|||
@Override
|
||||
public void update(Post post) {
|
||||
if (StringUtils.isBlank(url)) {
|
||||
url = HaloUtils.normalizeUrl(title);
|
||||
} else {
|
||||
url = HaloUtils.normalizeUrl(url);
|
||||
url = title;
|
||||
}
|
||||
|
||||
url = HaloUtils.initializeUrlIfBlank(url);
|
||||
|
||||
InputConverter.super.update(post);
|
||||
|
||||
// Crypt password
|
||||
|
|
|
@ -9,7 +9,7 @@ package run.halo.app.model.properties;
|
|||
*/
|
||||
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"),
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -122,4 +122,11 @@ public interface UserService extends CrudService<User, Integer> {
|
|||
* @param plainPassword plain password must not be blank
|
||||
*/
|
||||
void setPassword(@NonNull User user, @NonNull String plainPassword);
|
||||
|
||||
/**
|
||||
* Set user default avatar,use Gravatar(http://cn.gravatar.com)
|
||||
*
|
||||
* @param user user must not be null
|
||||
*/
|
||||
void setDefaultAvatar(@NonNull User user);
|
||||
}
|
||||
|
|
|
@ -241,8 +241,8 @@ public abstract class BaseCommentServiceImpl<COMMENT extends BaseComment> extend
|
|||
comment.setUserAgent(ServletUtils.getHeaderIgnoreCase(HttpHeaders.USER_AGENT));
|
||||
}
|
||||
|
||||
if (comment.getGavatarMd5() == null) {
|
||||
comment.setGavatarMd5(DigestUtils.md5Hex(comment.getEmail()));
|
||||
if (comment.getGravatarMd5() == null) {
|
||||
comment.setGravatarMd5(DigestUtils.md5Hex(comment.getEmail()));
|
||||
}
|
||||
|
||||
if (StringUtils.isNotEmpty(comment.getAuthorUrl())) {
|
||||
|
|
|
@ -346,7 +346,7 @@ public class RecoveryServiceImpl implements RecoveryService {
|
|||
baseComment.setEmail(commentMap.getOrDefault("commentAuthorEmail", "").toString());
|
||||
baseComment.setIpAddress(commentMap.getOrDefault("commentAuthorIp", "").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.setUserAgent(commentMap.getOrDefault("commentAgent", "").toString());
|
||||
baseComment.setIsAdmin(getBooleanOrDefault(commentMap.getOrDefault("isAdmin", "").toString(), false));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
|
@ -193,4 +195,12 @@ public class UserServiceImpl extends AbstractCrudService<User, Integer> implemen
|
|||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,25 +66,16 @@ public class FileUtils {
|
|||
public static void deleteFolder(@NonNull Path deletingPath) throws IOException {
|
||||
Assert.notNull(deletingPath, "Deleting path must not be null");
|
||||
|
||||
if (Files.notExists(deletingPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Deleting [{}]", deletingPath);
|
||||
|
||||
// Delete folder recursively
|
||||
org.eclipse.jgit.util.FileUtils.delete(deletingPath.toFile(),
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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
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,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>
|
|
@ -52,6 +52,9 @@
|
|||
</#macro>
|
||||
|
||||
<#macro globalHeader>
|
||||
<#if options.spider_disabled!false>
|
||||
<meta name="robots" content="none">
|
||||
</#if>
|
||||
<meta name="generator" content="Halo ${version!}" />
|
||||
<@custom_head />
|
||||
<@verification />
|
||||
|
|
Loading…
Reference in New Issue