mirror of https://github.com/halo-dev/halo
✨ 主题可在线安装以及更新(基于git)
parent
42ee51cfff
commit
8775511ed9
|
@ -64,10 +64,10 @@ public class ListPage<T> {
|
|||
this.nowPage = nowPage;
|
||||
this.totalCount = data.size();
|
||||
this.totalPage = (totalCount + pageSize - 1) / pageSize;
|
||||
this.prePage = nowPage-1>1? nowPage-1:1;
|
||||
this.nextPage = nowPage>=totalPage? totalPage: nowPage + 1;
|
||||
this.hasPrevious = nowPage!=prePage;
|
||||
this.hasNext = nowPage!=nextPage;
|
||||
this.prePage = nowPage - 1 > 1 ? nowPage - 1 : 1;
|
||||
this.nextPage = nowPage >= totalPage ? totalPage : nowPage + 1;
|
||||
this.hasPrevious = nowPage != prePage;
|
||||
this.hasNext = nowPage != nextPage;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -80,7 +80,7 @@ public class ListPage<T> {
|
|||
if (fromIndex >= data.size()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if(fromIndex<0){
|
||||
if (fromIndex < 0) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
int toIndex = nowPage * pageSize;
|
||||
|
|
|
@ -26,4 +26,9 @@ public class Theme implements Serializable {
|
|||
* 是否支持设置
|
||||
*/
|
||||
private boolean hasOptions;
|
||||
|
||||
/**
|
||||
* 是否支持更新
|
||||
*/
|
||||
private boolean hasUpdate;
|
||||
}
|
||||
|
|
|
@ -170,6 +170,12 @@ public class HaloUtils {
|
|||
} else {
|
||||
theme.setHasOptions(false);
|
||||
}
|
||||
File gitPath = new File(themesPath.getAbsolutePath(), file.getName() + "/.git");
|
||||
if (gitPath.exists()) {
|
||||
theme.setHasUpdate(true);
|
||||
} else {
|
||||
theme.setHasUpdate(false);
|
||||
}
|
||||
themes.add(theme);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import cn.hutool.core.date.DateUtil;
|
|||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.file.FileReader;
|
||||
import cn.hutool.core.io.file.FileWriter;
|
||||
import cn.hutool.core.util.RuntimeUtil;
|
||||
import cn.hutool.core.util.ZipUtil;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -29,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.websocket.server.PathParam;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -50,6 +52,8 @@ public class ThemeController extends BaseController {
|
|||
@Autowired
|
||||
private LogsService logsService;
|
||||
|
||||
private static final String NOT_FOUND_GIT = "-bash: git: command not found";
|
||||
|
||||
/**
|
||||
* 渲染主题设置页面
|
||||
*
|
||||
|
@ -149,6 +153,71 @@ public class ThemeController extends BaseController {
|
|||
return "redirect:/admin/themes";
|
||||
}
|
||||
|
||||
/**
|
||||
* 安装主题弹出层
|
||||
*
|
||||
* @return 目录路径admin/widget/_theme-install
|
||||
*/
|
||||
@GetMapping(value = "/install")
|
||||
public String install() {
|
||||
return "admin/widget/_theme-install";
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线拉取主题
|
||||
*
|
||||
* @param remoteAddr 远程地址
|
||||
* @param themeName 主题名称
|
||||
* @return JsonResult
|
||||
*/
|
||||
@PostMapping(value = "/clone")
|
||||
@ResponseBody
|
||||
public JsonResult cloneFromRemote(@RequestParam(value = "remoteAddr") String remoteAddr,
|
||||
@RequestParam(value = "themeName") String themeName) {
|
||||
if (StringUtils.isBlank(remoteAddr) || StringUtils.isBlank(themeName)) {
|
||||
return new JsonResult(0, "请输入完整信息!");
|
||||
}
|
||||
try {
|
||||
File basePath = new File(ResourceUtils.getURL("classpath:").getPath());
|
||||
File themePath = new File(basePath.getAbsolutePath(), "templates/themes");
|
||||
String cmdResult = RuntimeUtil.execForStr("git clone " + remoteAddr + " " + themePath.getAbsolutePath() + "/" + themeName);
|
||||
if (NOT_FOUND_GIT.equals(cmdResult)) {
|
||||
return new JsonResult(0, "没有安装Git!");
|
||||
}
|
||||
HaloConst.THEMES.clear();
|
||||
HaloConst.THEMES = HaloUtils.getThemes();
|
||||
} catch (FileNotFoundException e) {
|
||||
log.error("克隆主题失败:{}", e.getMessage());
|
||||
return new JsonResult(0, "克隆主题失败:" + e.getMessage());
|
||||
}
|
||||
return new JsonResult(1, "安装成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新主题
|
||||
*
|
||||
* @param themeName 主题名
|
||||
* @return JsonResult
|
||||
*/
|
||||
@GetMapping(value = "/pull")
|
||||
@ResponseBody
|
||||
public JsonResult pullFromRemote(@RequestParam(value = "themeName") String themeName) {
|
||||
try {
|
||||
File basePath = new File(ResourceUtils.getURL("classpath:").getPath());
|
||||
File themePath = new File(basePath.getAbsolutePath(), "templates/themes");
|
||||
String cmdResult = RuntimeUtil.execForStr("cd " + themePath.getAbsolutePath() + "/" + themeName + " && git pull");
|
||||
if (NOT_FOUND_GIT.equals(cmdResult)) {
|
||||
return new JsonResult(0, "没有安装Git!");
|
||||
}
|
||||
HaloConst.THEMES.clear();
|
||||
HaloConst.THEMES = HaloUtils.getThemes();
|
||||
} catch (Exception e) {
|
||||
log.error("更新主题失败:{}", e.getMessage());
|
||||
return new JsonResult(0, "更新主题失败:" + e.getMessage());
|
||||
}
|
||||
return new JsonResult(1, "更新成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到主题设置
|
||||
*
|
||||
|
|
|
@ -60,8 +60,8 @@
|
|||
</style>
|
||||
<section class="content-header">
|
||||
<h1 style="display: inline-block;">主题管理</h1>
|
||||
<a id="showForm" href="#">
|
||||
<i class="fa fa-cloud-upload" aria-hidden="true"></i>上传
|
||||
<a id="showForm" href="#" onclick="openThemeInstall()">
|
||||
<i class="fa fa-cloud-upload" aria-hidden="true"></i>安装主题
|
||||
</a>
|
||||
<ol class="breadcrumb">
|
||||
<li><a data-pjax="true" href="/admin"><i class="fa fa-dashboard"></i> 首页</a></li>
|
||||
|
@ -70,15 +70,6 @@
|
|||
</ol>
|
||||
</section>
|
||||
<section class="content container-fluid">
|
||||
<div class="row" id="uploadForm">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<div class="file-loading">
|
||||
<input id="uploadTheme" class="file-loading" type="file" name="file" multiple>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<#if themes?? && (themes?size>0)>
|
||||
<#list themes as theme>
|
||||
|
@ -89,9 +80,12 @@
|
|||
</div>
|
||||
<div class="box-footer">
|
||||
<span class="theme-title">${theme.themeName?if_exists?upper_case}</span>
|
||||
<#if theme.hasOptions==true>
|
||||
<#if theme.hasOptions>
|
||||
<button class="btn btn-primary btn-sm pull-right btn-theme-setting" onclick="openSetting('${theme.themeName?if_exists}')" style="display: none">设置</button>
|
||||
</#if>
|
||||
<#if theme.hasUpdate>
|
||||
<button class="btn btn-warning btn-sm pull-right btn-theme-update" data-loading-text="更新中..." onclick="updateTheme('${theme.themeName?if_exists}',this)" style="display: none;margin-right: 3px">更新</button>
|
||||
</#if>
|
||||
<#if activeTheme != "${theme.themeName}">
|
||||
<button class="btn btn-default btn-sm pull-right btn-theme-enable" onclick="setTheme('${theme.themeName?if_exists}')" style="display: none;margin-right: 3px">启用</button>
|
||||
<#else>
|
||||
|
@ -133,63 +127,26 @@
|
|||
</div>
|
||||
</div>
|
||||
<script type="application/javascript">
|
||||
function loadFileInput() {
|
||||
$('#uploadTheme').fileinput({
|
||||
language: 'zh',
|
||||
uploadUrl: '/admin/themes/upload',
|
||||
allowedFileExtensions: ['zip','jpg'],
|
||||
maxFileCount: 1,
|
||||
enctype: 'multipart/form-data',
|
||||
dropZoneTitle: '拖拽主题压缩包到这里 …<br>不支持多个主题同时上传',
|
||||
showClose: false
|
||||
}).on("fileuploaded",function (event,data,previewId,index) {
|
||||
var data = data.jqXHR.responseJSON;
|
||||
if(data.code==1){
|
||||
$("#uploadForm").hide(400);
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'success',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff',
|
||||
afterHidden: function () {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'error',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff'
|
||||
/**
|
||||
* 打开安装主题的窗口
|
||||
*/
|
||||
function openThemeInstall() {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: '安装主题',
|
||||
shadeClose: true,
|
||||
shade: 0.5,
|
||||
maxmin: true,
|
||||
area: ['90%', '90%'],
|
||||
content: '/admin/themes/install',
|
||||
scrollbar: false
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
$(document).ready(function () {
|
||||
loadFileInput();
|
||||
});
|
||||
<#if options.admin_pjax?default("true") == "true">
|
||||
$(document).on('pjax:complete',function () {
|
||||
loadFileInput();
|
||||
});
|
||||
</#if>
|
||||
$("#showForm").click(function(){
|
||||
$("#uploadForm").slideToggle(400);
|
||||
});
|
||||
|
||||
/**
|
||||
* 设置主题
|
||||
* @param site_theme 主题名
|
||||
*/
|
||||
function setTheme(site_theme) {
|
||||
$.ajax({
|
||||
type: 'get',
|
||||
|
@ -233,6 +190,61 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新主题
|
||||
*/
|
||||
function updateTheme(theme,e) {
|
||||
$(e).button('loading');
|
||||
$.ajax({
|
||||
type: 'get',
|
||||
url: '/admin/themes/pull',
|
||||
data: {
|
||||
'themeName': theme
|
||||
},
|
||||
success: function (data) {
|
||||
if(data.code==1){
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'success',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff',
|
||||
afterHidden: function () {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'error',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 2000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff'
|
||||
});
|
||||
$(e).button('reset');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开主题设置
|
||||
*
|
||||
* @param theme 主题名
|
||||
*/
|
||||
function openSetting(theme) {
|
||||
layer.open({
|
||||
type: 2,
|
||||
|
@ -253,11 +265,11 @@
|
|||
});
|
||||
$('.theme-body').mouseover(function () {
|
||||
$(this).find(".theme-thumbnail").css("opacity","0.8");
|
||||
$(this).find(".btn-theme-setting,.btn-theme-enable").show();
|
||||
$(this).find(".btn-theme-setting,.btn-theme-enable,.btn-theme-update").show();
|
||||
});
|
||||
$('.theme-body').mouseleave(function () {
|
||||
$(this).find(".theme-thumbnail").css("opacity","1");
|
||||
$(this).find(".btn-theme-setting,.btn-theme-enable").hide();
|
||||
$(this).find(".btn-theme-setting,.btn-theme-enable,.btn-theme-update").hide();
|
||||
});
|
||||
function modelShow(url) {
|
||||
$('#url').val(url);
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
||||
<link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/plugins/toast/css/jquery.toast.min.css">
|
||||
<link rel="stylesheet" href="/static/plugins/fileinput/fileinput.min.css">
|
||||
<link rel="stylesheet" href="/static/css/AdminLTE.min.css">
|
||||
<style type="text/css" rel="stylesheet">
|
||||
.form-horizontal .control-label{
|
||||
text-align: left;
|
||||
}
|
||||
.alert-info{
|
||||
color: #31708f!important;
|
||||
background-color: #d9edf7!important;
|
||||
border-color: #bce8f1!important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<section class="content">
|
||||
<div class="nav-tabs-custom">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active">
|
||||
<a href="#upload" data-toggle="tab">本地上传</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#clone" data-toggle="tab">远程拉取</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="upload">
|
||||
<div class="row" id="uploadForm">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<div class="file-loading">
|
||||
<input id="uploadTheme" class="file-loading" type="file" name="file" multiple>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane" id="clone">
|
||||
<form method="post" class="form-horizontal" id="pullForm">
|
||||
<div class="box-body">
|
||||
<div class="alert alert-info alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<strong>注意!</strong> 使用该功能必须安装Git,否则无法使用。更多主题请点击<a href="https://gitee.com/babyrui" class="alert-link">https://gitee.com/babyrui</a>.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="remoteAddr" class="col-lg-2 col-sm-4 control-label">远程地址:</label>
|
||||
<div class="col-lg-4 col-sm-8">
|
||||
<input type="text" class="form-control" id="remoteAddr" name="remoteAddr">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="themeName" class="col-lg-2 col-sm-4 control-label">主题名称:</label>
|
||||
<div class="col-lg-4 col-sm-8">
|
||||
<input type="text" class="form-control" id="themeName" name="themeName">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="button" data-loading-text="安装中..." class="btn btn-primary btn-sm" onclick="pullAction()" id="btnInstall">安装</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</body>
|
||||
<script src="/static/plugins/jquery/jquery.min.js"></script>
|
||||
<script src="/static/plugins/bootstrap/js/bootstrap.min.js"></script>
|
||||
<script src="/static/plugins/fileinput/fileinput.min.js"></script>
|
||||
<script src="/static/plugins/fileinput/zh.min.js"></script>
|
||||
<script src="/static/plugins/toast/js/jquery.toast.min.js"></script>
|
||||
<script src="/static/plugins/layer/layer.js"></script>
|
||||
<script src="/static/js/app.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
loadFileInput();
|
||||
});
|
||||
|
||||
/**
|
||||
* 初始化上传组件
|
||||
*/
|
||||
function loadFileInput() {
|
||||
$('#uploadTheme').fileinput({
|
||||
language: 'zh',
|
||||
uploadUrl: '/admin/themes/upload',
|
||||
allowedFileExtensions: ['zip'],
|
||||
maxFileCount: 1,
|
||||
enctype: 'multipart/form-data',
|
||||
dropZoneTitle: '拖拽主题压缩包到这里 …<br>仅支持Zip格式',
|
||||
showClose: false
|
||||
}).on("fileuploaded",function (event,data,previewId,index) {
|
||||
var data = data.jqXHR.responseJSON;
|
||||
if(data.code==1){
|
||||
$("#uploadForm").hide(400);
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'success',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff',
|
||||
afterHidden: function () {
|
||||
parent.location.reload();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'error',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取主题
|
||||
*/
|
||||
function pullAction() {
|
||||
var remoteAddr = $("#remoteAddr").val();
|
||||
var themeName = $("#themeName").val();
|
||||
if(remoteAddr==null || themeName==null){
|
||||
$.toast({
|
||||
text: "请输入完整信息!",
|
||||
heading: '提示',
|
||||
icon: 'error',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff'
|
||||
});
|
||||
return;
|
||||
}
|
||||
$('#btnInstall').button('loading');
|
||||
$.ajax({
|
||||
type: 'post',
|
||||
url: '/admin/themes/clone',
|
||||
data: {
|
||||
remoteAddr : remoteAddr,
|
||||
themeName: themeName
|
||||
},
|
||||
success: function (data) {
|
||||
if(data.code==1){
|
||||
$.toast({
|
||||
text: data.msg,
|
||||
heading: '提示',
|
||||
icon: 'success',
|
||||
showHideTransition: 'fade',
|
||||
allowToastClose: true,
|
||||
hideAfter: 1000,
|
||||
stack: 1,
|
||||
position: 'top-center',
|
||||
textAlign: 'left',
|
||||
loader: true,
|
||||
loaderBg: '#ffffff',
|
||||
afterHidden: function () {
|
||||
parent.location.reload();
|
||||
}
|
||||
});
|
||||
}else {
|
||||
showMsg(data.msg,"error",1000);
|
||||
$('#btnInstall').button('reset');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</html>
|
Loading…
Reference in New Issue