writing.html 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. <html lang="zh-CN">
  2. <head>
  3. <meta charset="utf-8">
  4. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  5. <meta name="viewport" content="width=device-width, initial-scale=1">
  6. <meta name="author" content="jie">
  7. <title>Backup-X</title>
  8. <!-- Bootstrap CSS -->
  9. <link rel="stylesheet" href="/static/bootstrap.min.css">
  10. <link rel="stylesheet" href="/static/common.css">
  11. <script src="/static/jquery-3.5.1.min.js"></script>
  12. <script src="/static/bootstrap.min.js"></script>
  13. <script src="/static/layer/layer.js"></script>
  14. </head>
  15. <body>
  16. <header>
  17. <div class="navbar navbar-dark bg-dark shadow-sm">
  18. <div class="container d-flex justify-content-between">
  19. <a href="/" class="navbar-brand d-flex align-items-center">
  20. <strong>Backup-X</strong>
  21. </a>
  22. <a href="https://github.com/jeessy2/backup-x" target="_blank" style="color: white">
  23. <strong>Github | Backup-X</strong>
  24. <span class="badge badge-secondary">
  25. {{.Version}}
  26. </span>
  27. </a>
  28. </div>
  29. </div>
  30. </header>
  31. <main role="main" style="margin-top: 30px">
  32. <div class="row">
  33. <div class="col-md-6 offset-md-3">
  34. <form>
  35. <button class="btn btn-primary submit_btn" style="margin-bottom: 15px;">Save</button>
  36. <button class="btn btn-primary submit_btn_backup_idx" style="margin-bottom: 15px;margin-left: 15px;">Save & 立即备份选中</button>
  37. <button class="btn btn-warning submit_btn_backup_all" style="margin-bottom: 15px;margin-left: 15px;">Save & 立即备份全部</button>
  38. <div class="alert" style="display: none;">
  39. <strong id="resultMsg"></strong>
  40. </div>
  41. <div class="portlet">
  42. <h5 class="portlet__head">备份设置</h5>
  43. <div class="portlet__body">
  44. <nav>
  45. <div class="nav nav-tabs" id="nav-tab" role="tablist">
  46. {{range $i, $v := .BackupConfig}}
  47. <a class="nav-item nav-link {{if eq $i 0}}active{{end}}" id="id_{{$i}}" data-toggle="tab" href="#content_{{$i}}" onclick="contentChange('{{$i}}')" role="tab">
  48. {{if eq $v.ProjectName ""}}
  49. {{$i}}
  50. {{else}}
  51. {{$v.ProjectName}}
  52. {{end}}
  53. {{if ne $v.Enabled 0}}
  54. <span class="badge badge-pill badge-warning">停用</span>
  55. {{end}}
  56. </a>
  57. {{end}}
  58. </div>
  59. </nav>
  60. <div class="tab-content" id="nav-tabContent">
  61. {{range $i, $v := .BackupConfig}}
  62. <div class="tab-pane fade {{if eq $i 0}}show active{{end}}" id="content_{{$i}}" role="tabpanel">
  63. <br/>
  64. <div class="form-group row">
  65. <label for="ProjectName_{{$i}}" class="col-sm-2 col-form-label">项目名称</label>
  66. <div class="col-sm-10">
  67. <input class="form-control" name="ProjectName" id="ProjectName_{{$i}}" rows="3" value="{{$v.ProjectName}}" onchange="projectNameChange(this)" aria-describedby="ProjectName_help">
  68. <small id="ProjectName_help" class="form-text text-muted">请输入项目名称,一般取数据库名称,并确保名称不重复</small>
  69. </div>
  70. </div>
  71. <div class="form-group row">
  72. <label for="Command_{{$i}}" class="col-sm-2 col-form-label">备份脚本</label>
  73. <div class="col-sm-10">
  74. <textarea class="form-control" name="Command" id="Command_{{$i}}" rows="3" aria-describedby="Command_help">{{$v.Command}}</textarea>
  75. <small id="Command_help" class="form-text text-muted">
  76. 日期变量 #{DATE} ,密码变量 #{PWD} ,对象存储变量: #{Endpoint} #{AccessKey} #{SecretKey} #{BucketName}
  77. <br/>例: mysqldump -h192.168.1.11 -uroot -p#{PWD} db-name > #{DATE}.sql <a target="blank" href="https://github.com/jeessy2/backup-x#备份脚本参考">备份脚本参考</a>
  78. </small>
  79. </div>
  80. </div>
  81. <div class="form-group row">
  82. <label for="Pwd_{{$i}}" class="col-sm-2 col-form-label">密码变量</label>
  83. <div class="col-sm-10">
  84. <input type="password" class="form-control" name="Pwd" id="Pwd_{{$i}}" value="{{$v.Pwd}}">
  85. </div>
  86. </div>
  87. <div class="form-group row">
  88. <label for="Enabled_{{$i}}" class="col-sm-2">是否启用</label>
  89. <div class="col-sm-4">
  90. <select class="form-control" name="Enabled" id="Enabled_{{$i}}" value="{{$v.Enabled}}" onchange="enabledChange(this)">
  91. <option value="0" {{if eq $v.Enabled 0}}selected{{end}}>启用</option>
  92. <option value="1" {{if eq $v.Enabled 1}}selected{{end}}>停用</option>
  93. </select>
  94. </div>
  95. <label for="BackupType_{{$i}}" class="col-sm-2">备份类型</label>
  96. <div class="col-sm-4">
  97. <select class="form-control" name="BackupType" id="BackupType_{{$i}}" value="{{$v.BackupType}}">
  98. <option value="0" {{if eq $v.BackupType 0}}selected{{end}}>备份数据库</option>
  99. <option value="1" {{if eq $v.BackupType 1}}selected{{end}}>同步文件</option>
  100. </select>
  101. </div>
  102. </div>
  103. <div class="form-group row">
  104. <label for="SaveDays_{{$i}}" class="col-sm-2 col-form-label">本地保存(天)</label>
  105. <div class="col-sm-4">
  106. <input type="number" class="form-control" name="SaveDays" id="SaveDays_{{$i}}" value="{{$v.SaveDays}}" min="1">
  107. </div>
  108. <label for="SaveDaysS3_{{$i}}" class="col-sm-2 col-form-label">对象存储保存(天)</label>
  109. <div class="col-sm-4">
  110. <input type="number" class="form-control" name="SaveDaysS3" id="SaveDaysS3_{{$i}}" value="{{$v.SaveDaysS3}}" min="1">
  111. </div>
  112. </div>
  113. <div class="form-group row">
  114. <label for="StartTime_{{$i}}" class="col-sm-2 col-form-label">备份起始时间</label>
  115. <div class="col-sm-4">
  116. <select class="form-control" name="StartTime" id="StartTime_{{$i}}" value="{{$v.StartTime}}">
  117. <option value="0" {{if eq $v.StartTime 0}}selected{{end}}>0:00</option>
  118. <option value="1" {{if eq $v.StartTime 1}}selected{{end}}>1:00</option>
  119. <option value="2" {{if eq $v.StartTime 2}}selected{{end}}>2:00</option>
  120. <option value="3" {{if eq $v.StartTime 3}}selected{{end}}>3:00</option>
  121. <option value="4" {{if eq $v.StartTime 4}}selected{{end}}>4:00</option>
  122. <option value="5" {{if eq $v.StartTime 5}}selected{{end}}>5:00</option>
  123. <option value="6" {{if eq $v.StartTime 6}}selected{{end}}>6:00</option>
  124. <option value="7" {{if eq $v.StartTime 7}}selected{{end}}>7:00</option>
  125. <option value="8" {{if eq $v.StartTime 8}}selected{{end}}>8:00</option>
  126. <option value="9" {{if eq $v.StartTime 9}}selected{{end}}>9:00</option>
  127. <option value="10" {{if eq $v.StartTime 10}}selected{{end}}>10:00</option>
  128. <option value="11" {{if eq $v.StartTime 11}}selected{{end}}>11:00</option>
  129. <option value="12" {{if eq $v.StartTime 12}}selected{{end}}>12:00</option>
  130. <option value="13" {{if eq $v.StartTime 13}}selected{{end}}>13:00</option>
  131. <option value="14" {{if eq $v.StartTime 14}}selected{{end}}>14:00</option>
  132. <option value="15" {{if eq $v.StartTime 15}}selected{{end}}>15:00</option>
  133. <option value="16" {{if eq $v.StartTime 16}}selected{{end}}>16:00</option>
  134. <option value="17" {{if eq $v.StartTime 17}}selected{{end}}>17:00</option>
  135. <option value="18" {{if eq $v.StartTime 18}}selected{{end}}>18:00</option>
  136. <option value="19" {{if eq $v.StartTime 19}}selected{{end}}>19:00</option>
  137. <option value="20" {{if eq $v.StartTime 20}}selected{{end}}>20:00</option>
  138. <option value="21" {{if eq $v.StartTime 21}}selected{{end}}>21:00</option>
  139. <option value="22" {{if eq $v.StartTime 22}}selected{{end}}>22:00</option>
  140. <option value="23" {{if eq $v.StartTime 23}}selected{{end}}>23:00</option>
  141. </select>
  142. </div>
  143. <label for="Period_{{$i}}" class="col-sm-2 col-form-label">备份周期(分钟)</label>
  144. <div class="col-sm-4">
  145. <input type="number" class="form-control" name="Period" id="Period_{{$i}}" value="{{$v.Period}}" min="1">
  146. </div>
  147. </div>
  148. </div>
  149. {{end}}
  150. </div>
  151. </div>
  152. </div>
  153. <div class="portlet">
  154. <h5 class="portlet__head">服务配置</h5>
  155. <div class="portlet__body">
  156. <div class="form-group row">
  157. <label for="Username" class="col-sm-2 col-form-label">登录用户名</label>
  158. <div class="col-sm-10">
  159. <input class="form-control" name="Username" id="Username" value="{{.Username}}" aria-describedby="Username_help" required>
  160. <small id="Username_help" class="form-text text-muted">必须输入</small>
  161. </div>
  162. </div>
  163. <div class="form-group row">
  164. <label for="Password" class="col-sm-2 col-form-label">登录密码</label>
  165. <div class="col-sm-10">
  166. <input class="form-control" type="password" name="Password" id="Password" value="{{.Password}}" aria-describedby="password_help" required>
  167. <small id="password_help" class="form-text text-muted">必须输入</small>
  168. </div>
  169. </div>
  170. </div>
  171. </div>
  172. <div class="portlet">
  173. <h5 class="portlet__head">Webhook通知</h5>
  174. <div class="portlet__body">
  175. <div class="form-group row">
  176. <label for="WebhookURL" class="col-sm-2 col-form-label">URL</label>
  177. <div class="col-sm-10">
  178. <input class="form-control" name="WebhookURL" id="WebhookURL" value="{{.WebhookURL}}" aria-describedby="WebhookURL_help">
  179. <small id="WebhookURL_help" class="form-text text-muted">
  180. <a target="blank" href="https://github.com/jeessy2/backup-x#webhook">点击参考官方Webhook说明</a><br/>
  181. 支持的变量#{projectName}, #{fileName}, #{fileSize}, #{result},
  182. </small>
  183. </div>
  184. </div>
  185. <div class="form-group row">
  186. <label for="WebhookRequestBody" class="col-sm-2 col-form-label">RequestBody</label>
  187. <div class="col-sm-10">
  188. <textarea class="form-control" id="WebhookRequestBody" name="WebhookRequestBody" rows="3" aria-describedby="WebhookRequestBody_help">
  189. {{- .WebhookRequestBody -}}
  190. </textarea>
  191. <small id="WebhookRequestBody_help" class="form-text text-muted">
  192. RequestBody为空GET请求,不为空POST请求。支持的变量同上
  193. </small>
  194. </div>
  195. </div>
  196. <div class="form-group row">
  197. <label class="col-sm-2 col-form-label"></label>
  198. <div class="col-sm-10">
  199. <button class="btn btn-primary btn-sm" id="webhookTestBtn" aria-describedby="webhookTestBtn_help">模拟测试Webhook</button>
  200. <small id="webhookTestBtn_help" class="form-text text-muted"></small>
  201. </div>
  202. </div>
  203. </div>
  204. </div>
  205. <div class="portlet">
  206. <h5 class="portlet__head">对象存储配置</h5>
  207. <div class="portlet__body">
  208. <div class="form-group row">
  209. <label for="Endpoint" class="col-sm-2 col-form-label">Endpoint</label>
  210. <div class="col-sm-10">
  211. <input class="form-control" name="Endpoint" id="Endpoint" value="{{.Endpoint}}" aria-describedby="Endpoint_help">
  212. </div>
  213. </div>
  214. <div class="form-group row">
  215. <label for="AccessKey" class="col-sm-2 col-form-label">AccessKey</label>
  216. <div class="col-sm-10">
  217. <input class="form-control" name="AccessKey" id="AccessKey" value="{{.AccessKey}}" aria-describedby="AccessKey_help">
  218. </div>
  219. </div>
  220. <div class="form-group row">
  221. <label for="SecretKey" class="col-sm-2 col-form-label">SecretKey</label>
  222. <div class="col-sm-10">
  223. <input class="form-control" type="password" name="SecretKey" id="SecretKey" value="{{.SecretKey}}" aria-describedby="SecretKey_help">
  224. </div>
  225. </div>
  226. <div class="form-group row">
  227. <label for="BucketName" class="col-sm-2 col-form-label">BucketName</label>
  228. <div class="col-sm-10">
  229. <input class="form-control" name="BucketName" id="BucketName" value="{{.BucketName}}" aria-describedby="BucketName_help">
  230. </div>
  231. </div>
  232. </div>
  233. </div>
  234. <button class="btn btn-primary submit_btn" style="margin-bottom: 15px;">Save</button>
  235. <button class="btn btn-primary submit_btn_backup_idx" style="margin-bottom: 15px;margin-left: 15px;">Save & 立即备份选中</button>
  236. <button class="btn btn-warning submit_btn_backup_all" style="margin-bottom: 15px;margin-left: 15px;">Save & 立即备份全部</button>
  237. </form>
  238. </div>
  239. <div class="col-md-3">
  240. <div class="nav nav-tabs" role="tablist" style="margin-top: 50px;">
  241. <a class="nav-item nav-link active" href="#x0" data-toggle="tab" onclick="changeLog(1)" role="tab">
  242. 备份日志
  243. </a>
  244. <a class="nav-item nav-link" href="#x1" data-toggle="tab" onclick="changeLog(2)" role="tab">
  245. 登录日志
  246. </a>
  247. <a class="nav-item nav-link" href="#x2" data-toggle="tab" onclick="changeLog(0)" role="tab">
  248. 全部日志
  249. </a>
  250. </div>
  251. <p class="font-weight-light text-break" style="margin-top: 10px;font-size: 13px;" id="logs"></p>
  252. <button type="button" class="btn btn-outline-primary btn-sm" id="clearLogBtn">清空日志</button>
  253. </div>
  254. </div>
  255. </main>
  256. <script>
  257. let contentIdx = 0
  258. let logType = 1
  259. let logList = [] // 0:所有日志;1:日常日志;2:登录日志
  260. function contentChange(i) {
  261. contentIdx = i
  262. }
  263. function changeLog(type = 0) {
  264. logType = type
  265. const curLogList = logList[logType]
  266. const totalLogList = logList[0]
  267. const queryPageCount = totalLogList.filter(one => one.includes('请求登录')).length
  268. const failedCount = totalLogList.filter(one => one.includes('登录失败')).length
  269. const backupCount = totalLogList.filter(one => one.includes('正在备份项目')).length
  270. const html = `备份执行次数:${backupCount}次<br/>`+
  271. `历史页面访问次数:${queryPageCount}<br/>` +
  272. `历史登录失败:${failedCount}个<br/>` +
  273. `<br/>` + curLogList.join('<br/>')
  274. $("#logs").html(html)
  275. }
  276. function showLayer(target) {
  277. let oriTip = target.getAttribute('tip')
  278. const newTip = oriTip.split('\n').join('<br/>')
  279. const message = newTip
  280. .replace(/(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}\s*INFO\s*:)/gm, '<span style="color: #d8d8d8">$1</span>')
  281. .replace(/(\d{4}\/\\d{2}\/\d{2} \d{2}:\d{2}:\d{2}\s*ERROR\s*:)/gm, '<span style="color: #f12e2e">$1</span>')
  282. layer.open({
  283. type: 1, // page 层类型
  284. area: ['800px', '600px'],
  285. title: '运行信息',
  286. offset: '8%',
  287. shade: 0.6, // 遮罩透明度
  288. shadeClose: true, // 点击遮罩区域,关闭弹层
  289. maxmin: true, // 允许全屏最小化
  290. anim: 0, // 0-6 的动画形式,-1 不开启
  291. content: `<div style="padding: 10px 20px; font-size: 14px;line-height: 26px;">${message}</div>`
  292. });
  293. }
  294. $(function(){
  295. $(".submit_btn,.submit_btn_backup_all,.submit_btn_backup_idx").on('click',function(e) {
  296. e.preventDefault();
  297. $('body').animate({ scrollTop: 0 }, 300);
  298. let url = "/save"
  299. if(e.target.classList.contains("submit_btn_backup_all")) {
  300. url += "?backupAll=true"
  301. }
  302. if(e.target.classList.contains("submit_btn_backup_idx")) {
  303. url += "?backupIdx="+contentIdx
  304. }
  305. $.ajax({
  306. method: "POST",
  307. url: url,
  308. data: $('form').serialize(),
  309. success: function (result) {
  310. $('.alert').css("display", "block");
  311. if (result !== "ok") {
  312. $('.alert').addClass("alert-danger").removeClass("alert-success")
  313. $('#resultMsg').html(result)
  314. } else {
  315. // ok
  316. $('.alert').addClass("alert-success").removeClass("alert-danger")
  317. $('#resultMsg').html("保存成功")
  318. setTimeout(() => {
  319. getLogs()
  320. }, 800)
  321. setTimeout(function(){
  322. $('.alert').css("display", "none");
  323. }, 3000)
  324. }
  325. },
  326. error: function(jqXHR) {
  327. alert(jqXHR.statusText);
  328. }
  329. })
  330. })
  331. })
  332. // projectNameChange
  333. function projectNameChange(that) {
  334. let id = $(that).attr("id").split("_")[1]
  335. let name = $(that).val()
  336. let enabled = +$("#Enabled_"+id).val() === 0
  337. $("#id_"+id).html(enabled ? name:name+'<span class="badge badge-pill badge-warning">停用</span>')
  338. }
  339. // enabledChange
  340. function enabledChange(that) {
  341. let id = $(that).attr("id").split("_")[1]
  342. let name = $("#ProjectName_"+id).val()
  343. let enabled = +$(that).val() === 0
  344. $("#id_"+id).html(enabled ? name:name+'<span class="badge badge-pill badge-warning">停用</span>')
  345. }
  346. </script>
  347. <script>
  348. function getLogs() {
  349. $.get("/logs", function(result){
  350. const curList = result.split("<br/>").filter(one => one.length)
  351. logList[0] = curList.map(one => one)
  352. logList[1] = curList.filter(one => !one.includes("登录"))
  353. logList[2] = curList.filter(one => one.includes("登录"))
  354. changeLog(logType)
  355. })
  356. }
  357. getLogs()
  358. setInterval(getLogs, 5 * 1000)
  359. $(function(){
  360. $("#clearLogBtn").on("click", function(e) {
  361. e.preventDefault();
  362. $.ajax({
  363. method: "GET",
  364. url: "/clearLog",
  365. success: function() {
  366. getLogs()
  367. },
  368. error: function(jqXHR) {
  369. alert(jqXHR.statusText);
  370. }
  371. })
  372. })
  373. })
  374. </script>
  375. <script>
  376. $(function(){
  377. $("#webhookTestBtn").on("click", function(e) {
  378. e.preventDefault();
  379. $.ajax({
  380. method: "POST",
  381. url: "/webhookTest",
  382. data: {"URL": $("#WebhookURL").val(), "RequestBody": $("#WebhookRequestBody").val()},
  383. success: function() {
  384. $("#webhookTestBtn_help").text("提交模拟测试成功, 如修改记得保存配置")
  385. setTimeout(function(){
  386. $("#webhookTestBtn_help").text("")
  387. }, 5000)
  388. },
  389. error: function(jqXHR) {
  390. alert(jqXHR.statusText);
  391. }
  392. })
  393. })
  394. })
  395. </script>
  396. </body>
  397. </html>