mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
embed frontend
This commit is contained in:
parent
882fe8c074
commit
d09f484790
86 changed files with 884 additions and 12326 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ database.db
|
|||
tmp
|
||||
node_modules
|
||||
app.ini
|
||||
dist
|
||||
|
|
12
README.md
12
README.md
|
@ -74,16 +74,10 @@ server {
|
|||
root /path/to/nginx-ui/frontend/dist;
|
||||
|
||||
location / {
|
||||
# First attempt to serve request as file, then
|
||||
# as directory, then fall back to displaying a 404.
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real_IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection upgrade;
|
||||
|
|
57
api/auth.go
Normal file
57
api/auth.go
Normal file
|
@ -0,0 +1,57 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type LoginUser struct {
|
||||
Name string `json:"name" binding:"required,max=255"`
|
||||
Password string `json:"password" binding:"required,max=255"`
|
||||
}
|
||||
|
||||
func Login(c *gin.Context) {
|
||||
var user LoginUser
|
||||
ok := BindAndValid(c, &user)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
u, _ := model.GetUser(user.Name)
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(user.Password)); err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"message": "用户名或密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
token, err := model.GenerateJWT(u.Name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
"token": token,
|
||||
})
|
||||
}
|
||||
|
||||
func Logout(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token != "" {
|
||||
err := model.DeleteToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusNoContent, nil)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/unknwon/com"
|
||||
"net/http"
|
|
@ -2,7 +2,7 @@ package api
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"github.com/0xJacky/Nginx-UI/tool"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"log"
|
|
@ -1,7 +1,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"github.com/0xJacky/Nginx-UI/tool"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/ioutil"
|
||||
"log"
|
263
api/domain.go
Normal file
263
api/domain.go
Normal file
|
@ -0,0 +1,263 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/0xJacky/Nginx-UI/tool"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func GetDomains(c *gin.Context) {
|
||||
orderBy := c.Query("order_by")
|
||||
sort := c.DefaultQuery("sort", "desc")
|
||||
|
||||
mySort := map[string]string{
|
||||
"enabled": "bool",
|
||||
"name": "string",
|
||||
"modify": "time",
|
||||
}
|
||||
|
||||
configFiles, err := ioutil.ReadDir(tool.GetNginxConfPath("sites-available"))
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
enabledConfig, err := ioutil.ReadDir(filepath.Join(tool.GetNginxConfPath("sites-enabled")))
|
||||
|
||||
enabledConfigMap := make(map[string]bool)
|
||||
for i := range enabledConfig {
|
||||
enabledConfigMap[enabledConfig[i].Name()] = true
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var configs []gin.H
|
||||
|
||||
for i := range configFiles {
|
||||
file := configFiles[i]
|
||||
if !file.IsDir() {
|
||||
configs = append(configs, gin.H{
|
||||
"name": file.Name(),
|
||||
"size": file.Size(),
|
||||
"modify": file.ModTime(),
|
||||
"enabled": enabledConfigMap[file.Name()],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"configs": configs,
|
||||
})
|
||||
}
|
||||
|
||||
func GetDomain(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
||||
|
||||
enabled := true
|
||||
if _, err := os.Stat(filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path)
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = model.FirstCert(name)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"enabled": enabled,
|
||||
"name": name,
|
||||
"config": string(content),
|
||||
"auto_cert": err == nil,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func EditDomain(c *gin.Context) {
|
||||
var err error
|
||||
name := c.Param("name")
|
||||
request := make(gin.H)
|
||||
err = c.BindJSON(&request)
|
||||
path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
||||
|
||||
err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
|
||||
if _, err = os.Stat(enabledConfigFilePath); err == nil {
|
||||
// 测试配置文件
|
||||
err = tool.TestNginxConf(enabledConfigFilePath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
output := tool.ReloadNginx()
|
||||
|
||||
if output != "" {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
GetDomain(c)
|
||||
}
|
||||
|
||||
func EnableDomain(c *gin.Context) {
|
||||
configFilePath := filepath.Join(tool.GetNginxConfPath("sites-available"), c.Param("name"))
|
||||
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||
|
||||
_, err := os.Stat(configFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Symlink(configFilePath, enabledConfigFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 测试配置文件,不通过则撤回启用
|
||||
err = tool.TestNginxConf(enabledConfigFilePath)
|
||||
if err != nil {
|
||||
_ = os.Remove(enabledConfigFilePath)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
output := tool.ReloadNginx()
|
||||
|
||||
if output != "" {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func DisableDomain(c *gin.Context) {
|
||||
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||
|
||||
_, err := os.Stat(enabledConfigFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Remove(enabledConfigFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
output := tool.ReloadNginx()
|
||||
|
||||
if output != "" {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteDomain(c *gin.Context) {
|
||||
var err error
|
||||
name := c.Param("name")
|
||||
availablePath := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
||||
enabledPath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
|
||||
|
||||
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": "site not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = os.Stat(enabledPath); err == nil {
|
||||
c.JSON(http.StatusNotAcceptable, gin.H{
|
||||
"message": "site is enabled",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cert := model.Cert{Domain: name}
|
||||
_ = cert.Remove()
|
||||
|
||||
err = os.Remove(availablePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func AddDomainToAutoCert(c *gin.Context) {
|
||||
domain := c.Param("domain")
|
||||
cert, err := model.FirstOrCreateCert(domain)
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, cert)
|
||||
}
|
||||
|
||||
func RemoveDomainFromAutoCert(c *gin.Context) {
|
||||
cert := model.Cert{
|
||||
Domain: c.Param("domain"),
|
||||
}
|
||||
err := cert.Remove()
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, nil)
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
37
api/template.go
Normal file
37
api/template.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetTemplate(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
path := filepath.Join("template", name)
|
||||
content, err := ioutil.ReadFile(path)
|
||||
|
||||
_content := string(content)
|
||||
_content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",
|
||||
settings.ServerSettings.HTTPChallengePort)
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
"template": _content,
|
||||
})
|
||||
}
|
|
@ -2,7 +2,7 @@ package api
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/spf13/cast"
|
||||
"golang.org/x/crypto/bcrypt"
|
BIN
frontend/dist/favicon.ico
vendored
BIN
frontend/dist/favicon.ico
vendored
Binary file not shown.
Before Width: | Height: | Size: 66 KiB |
BIN
frontend/dist/img/logo.9e691c6b.png
vendored
BIN
frontend/dist/img/logo.9e691c6b.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 5.9 KiB |
11356
frontend/dist/img/remixicon.symbol.f09b1c74.svg
vendored
11356
frontend/dist/img/remixicon.symbol.f09b1c74.svg
vendored
File diff suppressed because it is too large
Load diff
Before Width: | Height: | Size: 877 KiB |
1
frontend/dist/index.html
vendored
1
frontend/dist/index.html
vendored
|
@ -1 +0,0 @@
|
|||
<!DOCTYPE html><html lang=""><head><meta charset="utf-8"><meta content="IE=edge" http-equiv="X-UA-Compatible"><meta content="width=device-width,initial-scale=1,user-scalable=0" name="viewport"><link href="/favicon.ico" rel="icon"><title>Nginx UI</title><link href="/js/chunk-00be396e.f6afc813.js" rel="prefetch"><link href="/js/chunk-0393876a.0e2e8183.js" rel="prefetch"><link href="/js/chunk-05148b16.66291bd9.js" rel="prefetch"><link href="/js/chunk-09f0acda.b788b1ae.js" rel="prefetch"><link href="/js/chunk-2881409a.1f853726.js" rel="prefetch"><link href="/js/chunk-2d0cf277.c260d8d5.js" rel="prefetch"><link href="/js/chunk-312c57da.53fa07de.js" rel="prefetch"><link href="/js/chunk-4216c952.f0073bdb.js" rel="prefetch"><link href="/js/chunk-46dcb584.5ee0f4ea.js" rel="prefetch"><link href="/js/chunk-4f82bf3d.8d3be338.js" rel="prefetch"><link href="/js/chunk-5573b71a.92b99af4.js" rel="prefetch"><link href="/js/chunk-5d4d188e.b0ffa164.js" rel="prefetch"><link href="/js/chunk-6a4ca29d.5593b7e1.js" rel="prefetch"><link href="/js/chunk-83d83096.72980dc3.js" rel="prefetch"><link href="/js/chunk-b508de6a.b421b1eb.js" rel="prefetch"><link href="/js/chunk-c8c0a686.85e5c7a1.js" rel="prefetch"><link href="/js/chunk-vendors.731e48fc.js" rel="modulepreload" as="script"><link href="/js/index.62a46eff.js" rel="modulepreload" as="script"></head><body><noscript><strong>We're sorry but Nginx UI doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script type="module" src="/js/chunk-vendors.731e48fc.js"></script><script type="module" src="/js/index.62a46eff.js"></script><script>!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()},!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();</script><script src="/js/chunk-vendors-legacy.731e48fc.js" nomodule></script><script src="/js/index-legacy.ec1dfd27.js" nomodule></script></body></html>
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-00be396e.f6afc813.js
vendored
1
frontend/dist/js/chunk-00be396e.f6afc813.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-0393876a"],{"0efa":function(t,n,e){"use strict";e.r(n);var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("a-card",{attrs:{title:"配置文件编辑"}},[e("vue-itextarea",{model:{value:t.configText,callback:function(n){t.configText=n},expression:"configText"}}),e("footer-tool-bar",[e("a-space",[e("a-button",{on:{click:function(n){return t.$router.go(-1)}}},[t._v("返回")]),e("a-button",{attrs:{type:"primary"},on:{click:t.save}},[t._v("保存")])],1)],1)],1)},o=[],i=(e("b0c0"),e("9c70")),c=e("a002"),s={name:"DomainEdit",components:{FooterToolBar:i["a"],VueItextarea:c["a"]},data:function(){return{name:this.$route.params.name,configText:""}},watch:{$route:function(){this.init()},config:{handler:function(){this.unparse()},deep:!0}},created:function(){this.init()},methods:{init:function(){var t=this;this.name?this.$api.config.get(this.name).then((function(n){t.configText=n.config})).catch((function(n){console.log(n),t.$message.error("服务器错误")})):this.configText=""},save:function(){var t=this;this.$api.config.save(this.name?this.name:this.config.name,{content:this.configText}).then((function(n){t.configText=n.config,t.$message.success("保存成功")})).catch((function(n){console.log(n),t.$message.error("保存错误")}))}}},r=s,f=(e("8f3d"),e("2877")),u=Object(f["a"])(r,a,o,!1,null,"fe43c41a",null);n["default"]=u.exports},1175:function(t,n,e){var a=e("24fb");n=a(!1),n.push([t.i,".ant-card[data-v-fe43c41a]{margin:10px}@media (max-width:512px){.ant-card[data-v-fe43c41a]{margin:10px 0}}",""]),t.exports=n},"48b1":function(t,n,e){var a=e("1175");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[t.i,a,""]]),a.locals&&(t.exports=a.locals);var o=e("499e").default;o("77d6a146",a,!0,{sourceMap:!1,shadowMode:!1})},"8f3d":function(t,n,e){"use strict";e("48b1")}}]);
|
1
frontend/dist/js/chunk-0393876a.0e2e8183.js
vendored
1
frontend/dist/js/chunk-0393876a.0e2e8183.js
vendored
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-0393876a"],{"0efa":function(t,n,e){"use strict";e.r(n);var a=function(){var t=this,n=t.$createElement,e=t._self._c||n;return e("a-card",{attrs:{title:"配置文件编辑"}},[e("vue-itextarea",{model:{value:t.configText,callback:function(n){t.configText=n},expression:"configText"}}),e("footer-tool-bar",[e("a-space",[e("a-button",{on:{click:function(n){return t.$router.go(-1)}}},[t._v("返回")]),e("a-button",{attrs:{type:"primary"},on:{click:t.save}},[t._v("保存")])],1)],1)],1)},o=[],i=(e("b0c0"),e("9c70")),c=e("a002"),s={name:"DomainEdit",components:{FooterToolBar:i["a"],VueItextarea:c["a"]},data:function(){return{name:this.$route.params.name,configText:""}},watch:{$route:function(){this.init()},config:{handler:function(){this.unparse()},deep:!0}},created:function(){this.init()},methods:{init:function(){var t=this;this.name?this.$api.config.get(this.name).then((function(n){t.configText=n.config})).catch((function(n){console.log(n),t.$message.error("服务器错误")})):this.configText=""},save:function(){var t=this;this.$api.config.save(this.name?this.name:this.config.name,{content:this.configText}).then((function(n){t.configText=n.config,t.$message.success("保存成功")})).catch((function(n){console.log(n),t.$message.error("保存错误")}))}}},r=s,f=(e("8f3d"),e("2877")),u=Object(f["a"])(r,a,o,!1,null,"fe43c41a",null);n["default"]=u.exports},1175:function(t,n,e){var a=e("24fb");n=a(!1),n.push([t.i,".ant-card[data-v-fe43c41a]{margin:10px}@media (max-width:512px){.ant-card[data-v-fe43c41a]{margin:10px 0}}",""]),t.exports=n},"48b1":function(t,n,e){var a=e("1175");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[t.i,a,""]]),a.locals&&(t.exports=a.locals);var o=e("499e").default;o("77d6a146",a,!0,{sourceMap:!1,shadowMode:!1})},"8f3d":function(t,n,e){"use strict";e("48b1")}}]);
|
File diff suppressed because one or more lines are too long
3
frontend/dist/js/chunk-05148b16.66291bd9.js
vendored
3
frontend/dist/js/chunk-05148b16.66291bd9.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-09f0acda"],{"0561":function(t,a,e){var o=e("504c");o.__esModule&&(o=o.default),"string"===typeof o&&(o=[[t.i,o,""]]),o.locals&&(t.exports=o.locals);var i=e("499e").default;i("4267656c",o,!0,{sourceMap:!1,shadowMode:!1})},"1f35":function(t,a,e){"use strict";e.r(a);var o=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",{staticClass:"wrapper"},[e("h1",{staticClass:"title"},[t._v(t._s(t.$route.meta.status_code?t.$route.meta.status_code:404))]),e("p",[t._v(t._s(t.$route.meta.error?t.$route.meta.error:"找不到文件"))])])},i=[],c={name:"Error"},r=c,n=(e("629f"),e("2877")),s=Object(n["a"])(r,o,i,!1,null,"0b28b6c0",null);a["default"]=s.exports},"504c":function(t,a,e){var o=e("24fb");a=o(!1),a.push([t.i,"body[data-v-0b28b6c0],div[data-v-0b28b6c0],h1[data-v-0b28b6c0],html[data-v-0b28b6c0]{padding:0;margin:0}body[data-v-0b28b6c0],html[data-v-0b28b6c0]{color:#444;position:relative;font-family:PingFang SC,Helvetica Neue,Helvetica,Arial,CustomFont,Microsoft YaHei UI,Microsoft YaHei,Hiragino Sans GB,sans-serif;background:#fcfcfc;height:100%}h1[data-v-0b28b6c0]{font-size:8em;font-weight:100}a[data-v-0b28b6c0]{color:#4181b9;text-decoration:none;transition:all .3s ease}a[data-v-0b28b6c0]:active,a[data-v-0b28b6c0]:hover{color:#5bb0ed}.wrapper[data-v-0b28b6c0]{position:absolute;top:0;bottom:0;left:0;right:0;font-size:1em;font-weight:400;width:100%;height:30%;line-height:1;margin:auto;text-align:center}",""]),t.exports=a},"629f":function(t,a,e){"use strict";e("0561")}}]);
|
1
frontend/dist/js/chunk-09f0acda.b788b1ae.js
vendored
1
frontend/dist/js/chunk-09f0acda.b788b1ae.js
vendored
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-09f0acda"],{"0561":function(t,a,e){var o=e("504c");o.__esModule&&(o=o.default),"string"===typeof o&&(o=[[t.i,o,""]]),o.locals&&(t.exports=o.locals);var i=e("499e").default;i("4267656c",o,!0,{sourceMap:!1,shadowMode:!1})},"1f35":function(t,a,e){"use strict";e.r(a);var o=function(){var t=this,a=t.$createElement,e=t._self._c||a;return e("div",{staticClass:"wrapper"},[e("h1",{staticClass:"title"},[t._v(t._s(t.$route.meta.status_code?t.$route.meta.status_code:404))]),e("p",[t._v(t._s(t.$route.meta.error?t.$route.meta.error:"找不到文件"))])])},i=[],c={name:"Error"},r=c,n=(e("629f"),e("2877")),s=Object(n["a"])(r,o,i,!1,null,"0b28b6c0",null);a["default"]=s.exports},"504c":function(t,a,e){var o=e("24fb");a=o(!1),a.push([t.i,"body[data-v-0b28b6c0],div[data-v-0b28b6c0],h1[data-v-0b28b6c0],html[data-v-0b28b6c0]{padding:0;margin:0}body[data-v-0b28b6c0],html[data-v-0b28b6c0]{color:#444;position:relative;font-family:PingFang SC,Helvetica Neue,Helvetica,Arial,CustomFont,Microsoft YaHei UI,Microsoft YaHei,Hiragino Sans GB,sans-serif;background:#fcfcfc;height:100%}h1[data-v-0b28b6c0]{font-size:8em;font-weight:100}a[data-v-0b28b6c0]{color:#4181b9;text-decoration:none;transition:all .3s ease}a[data-v-0b28b6c0]:active,a[data-v-0b28b6c0]:hover{color:#5bb0ed}.wrapper[data-v-0b28b6c0]{position:absolute;top:0;bottom:0;left:0;right:0;font-size:1em;font-weight:400;width:100%;height:30%;line-height:1;margin:auto;text-align:center}",""]),t.exports=a},"629f":function(t,a,e){"use strict";e("0561")}}]);
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-2881409a.1f853726.js
vendored
1
frontend/dist/js/chunk-2881409a.1f853726.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0cf277"],{6304:function(e,n,t){"use strict";t.r(n);var c=function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("router-view")},r=[],u={name:"BaseRouterView"},a=u,o=t("2877"),s=Object(o["a"])(a,c,r,!1,null,"375df1cc",null);n["default"]=s.exports}}]);
|
1
frontend/dist/js/chunk-2d0cf277.c260d8d5.js
vendored
1
frontend/dist/js/chunk-2d0cf277.c260d8d5.js
vendored
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d0cf277"],{6304:function(e,n,t){"use strict";t.r(n);var c=function(){var e=this,n=e.$createElement,t=e._self._c||n;return t("router-view")},r=[],u={name:"BaseRouterView"},a=u,o=t("2877"),s=Object(o["a"])(a,c,r,!1,null,"375df1cc",null);n["default"]=s.exports}}]);
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-312c57da.53fa07de.js
vendored
1
frontend/dist/js/chunk-312c57da.53fa07de.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4216c952"],{3177:function(e,t,r){var n=r("6f0f");n.__esModule&&(n=n.default),"string"===typeof n&&(n=[[e.i,n,""]]),n.locals&&(e.exports=n.locals);var a=r("499e").default;a("4d98c476",n,!0,{sourceMap:!1,shadowMode:!1})},"6f0f":function(e,t,r){var n=r("24fb");t=n(!1),t.push([e.i,".container{display:flex;align-items:center;justify-content:center;height:100%}.container .login-form{max-width:400px;width:80%}.container .login-form .project-title{margin:50px}.container .login-form .project-title h1{font-size:50px;font-weight:100;text-align:center}.container .login-form .footer{padding:30px;text-align:center}",""]),e.exports=t},b2b2:function(e,t,r){"use strict";r("3177")},bf90:function(e,t,r){"use strict";r.r(t);var n=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"container"},[r("div",{staticClass:"login-form"},[e._m(0),r("a-form",{attrs:{id:"components-form-demo-normal-login",form:e.form},on:{submit:e.handleSubmit}},[r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["name",{rules:[{required:!0,message:"请输入用户名"}]}],expression:"[\n 'name',\n { rules: [{ required: true, message: '请输入用户名' }] },\n ]"}],attrs:{placeholder:"Username"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"user"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["password",{rules:[{required:!0,message:"请输入密码"}]}],expression:"[\n 'password',\n { rules: [{ required: true, message: '请输入密码' }] },\n ]"}],attrs:{type:"password",placeholder:"Password"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"lock"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-button",{attrs:{type:"primary",block:!0,"html-type":"submit",loading:e.loading}},[e._v(" 登录 ")])],1)],1),r("div",{staticClass:"footer"},[e._v(" Copyright © 2020 - "+e._s(e.thisYear)+" 0xJacky ")])],1)])},a=[function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"project-title"},[r("h1",[e._v("Nginx UI")])])}],o=r("1da1"),i=(r("96cf"),r("b0c0"),{name:"Login",data:function(){return{form:{},thisYear:(new Date).getFullYear(),loading:!1}},created:function(){this.form=this.$form.createForm(this)},mounted:function(){var e=this;this.$api.install.get_lock().then((function(t){t.lock||e.$router.push("/install")})),this.$store.state.user.token&&this.$router.push("/")},methods:{login:function(e){var t=this;return this.$api.auth.login(e.name,e.password).then(Object(o["a"])(regeneratorRuntime.mark((function e(){var r;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return e.next=2,t.$message.success("登录成功",1);case 2:return r=t.$route.query.next?t.$route.query.next:"/",e.next=5,t.$router.push(r);case 5:case"end":return e.stop()}}),e)})))).catch((function(e){var r;console.log(e),t.$message.error(null!==(r=e.message)&&void 0!==r?r:"服务器错误")}))},handleSubmit:function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(t){var r=this;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.preventDefault(),this.loading=!0,e.next=4,this.form.validateFields(function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(t,n){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:if(t){e.next=3;break}return e.next=3,r.login(n);case 3:r.loading=!1;case 4:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}());case 4:case"end":return e.stop()}}),e,this)})));function t(t){return e.apply(this,arguments)}return t}()}}),s=i,c=(r("b2b2"),r("2877")),u=Object(c["a"])(s,n,a,!1,null,null,null);t["default"]=u.exports}}]);
|
1
frontend/dist/js/chunk-4216c952.f0073bdb.js
vendored
1
frontend/dist/js/chunk-4216c952.f0073bdb.js
vendored
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4216c952"],{3177:function(e,t,r){var n=r("6f0f");n.__esModule&&(n=n.default),"string"===typeof n&&(n=[[e.i,n,""]]),n.locals&&(e.exports=n.locals);var a=r("499e").default;a("4d98c476",n,!0,{sourceMap:!1,shadowMode:!1})},"6f0f":function(e,t,r){var n=r("24fb");t=n(!1),t.push([e.i,".container{display:flex;align-items:center;justify-content:center;height:100%}.container .login-form{max-width:400px;width:80%}.container .login-form .project-title{margin:50px}.container .login-form .project-title h1{font-size:50px;font-weight:100;text-align:center}.container .login-form .footer{padding:30px;text-align:center}",""]),e.exports=t},b2b2:function(e,t,r){"use strict";r("3177")},bf90:function(e,t,r){"use strict";r.r(t);var n=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"container"},[r("div",{staticClass:"login-form"},[e._m(0),r("a-form",{attrs:{id:"components-form-demo-normal-login",form:e.form},on:{submit:e.handleSubmit}},[r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["name",{rules:[{required:!0,message:"请输入用户名"}]}],expression:"[\n 'name',\n { rules: [{ required: true, message: '请输入用户名' }] },\n ]"}],attrs:{placeholder:"Username"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"user"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["password",{rules:[{required:!0,message:"请输入密码"}]}],expression:"[\n 'password',\n { rules: [{ required: true, message: '请输入密码' }] },\n ]"}],attrs:{type:"password",placeholder:"Password"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"lock"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-button",{attrs:{type:"primary",block:!0,"html-type":"submit",loading:e.loading}},[e._v(" 登录 ")])],1)],1),r("div",{staticClass:"footer"},[e._v(" Copyright © 2020 - "+e._s(e.thisYear)+" 0xJacky ")])],1)])},a=[function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"project-title"},[r("h1",[e._v("Nginx UI")])])}],o=r("1da1"),i=(r("96cf"),r("b0c0"),{name:"Login",data:function(){return{form:{},thisYear:(new Date).getFullYear(),loading:!1}},created:function(){this.form=this.$form.createForm(this)},mounted:function(){var e=this;this.$api.install.get_lock().then((function(t){t.lock||e.$router.push("/install")})),this.$store.state.user.token&&this.$router.push("/")},methods:{login:function(e){var t=this;return this.$api.auth.login(e.name,e.password).then(Object(o["a"])(regeneratorRuntime.mark((function e(){var r;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return e.next=2,t.$message.success("登录成功",1);case 2:return r=t.$route.query.next?t.$route.query.next:"/",e.next=5,t.$router.push(r);case 5:case"end":return e.stop()}}),e)})))).catch((function(e){var r;console.log(e),t.$message.error(null!==(r=e.message)&&void 0!==r?r:"服务器错误")}))},handleSubmit:function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(t){var r=this;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.preventDefault(),this.loading=!0,e.next=4,this.form.validateFields(function(){var e=Object(o["a"])(regeneratorRuntime.mark((function e(t,n){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:if(t){e.next=3;break}return e.next=3,r.login(n);case 3:r.loading=!1;case 4:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}());case 4:case"end":return e.stop()}}),e,this)})));function t(t){return e.apply(this,arguments)}return t}()}}),s=i,c=(r("b2b2"),r("2877")),u=Object(c["a"])(s,n,a,!1,null,null,null);t["default"]=u.exports}}]);
|
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-46dcb584"],{"0125":function(e,t,r){var a=r("24fb");t=a(!1),t.push([e.i,".project-title{margin:50px}.project-title h1{font-size:50px;font-weight:100;text-align:center}.login-form{max-width:500px;margin:0 auto}footer{padding:30px;text-align:center}",""]),e.exports=t},"0f6e":function(e,t,r){"use strict";r("76f8")},"76f8":function(e,t,r){var a=r("0125");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[e.i,a,""]]),a.locals&&(e.exports=a.locals);var n=r("499e").default;n("447bb34c",a,!0,{sourceMap:!1,shadowMode:!1})},c756:function(e,t,r){"use strict";r.r(t);var a=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"login-form"},[e._m(0),r("a-form",{staticClass:"login-form",attrs:{id:"components-form-demo-normal-login",form:e.form},on:{submit:e.handleSubmit}},[r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["email",{rules:[{type:"email",message:"The input is not valid E-mail!"},{required:!0,message:"Please input your E-mail!"}]}],expression:"[\n 'email',\n { rules: [{\n type: 'email',\n message: 'The input is not valid E-mail!',\n },\n {\n required: true,\n message: 'Please input your E-mail!',\n },] },\n ]"}],attrs:{placeholder:"Email"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"mail"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["username",{rules:[{required:!0,message:"Please input your username!"}]}],expression:"[\n 'username',\n { rules: [{ required: true, message: 'Please input your username!' }] },\n ]"}],attrs:{placeholder:"Username"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"user"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["password",{rules:[{required:!0,message:"Please input your Password!"}]}],expression:"[\n 'password',\n { rules: [{ required: true, message: 'Please input your Password!' }] },\n ]"}],attrs:{type:"password",placeholder:"Password"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"lock"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-button",{attrs:{type:"primary",block:!0,"html-type":"submit",loading:e.loading}},[e._v(" 安装 ")])],1)],1),r("footer",[e._v(" Copyright © 2020 - "+e._s(e.thisYear)+" 0xJacky ")])],1)},n=[function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"project-title"},[r("h1",[e._v("Nginx UI")])])}],i=r("1da1"),o=(r("96cf"),{name:"Login",data:function(){return{form:{},lock:!0,thisYear:(new Date).getFullYear(),loading:!1}},created:function(){this.form=this.$form.createForm(this)},mounted:function(){var e=this;this.$api.install.get_lock().then((function(t){t.lock&&e.$router.push("/login")}))},methods:{handleSubmit:function(){var e=Object(i["a"])(regeneratorRuntime.mark((function e(t){var r=this;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.preventDefault(),this.loading=!0,e.next=4,this.form.validateFields(function(){var e=Object(i["a"])(regeneratorRuntime.mark((function e(t,a){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:t||r.$api.install.install_nginx_ui(a).then((function(){r.$router.push("/login")})),r.loading=!1;case 2:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}());case 4:case"end":return e.stop()}}),e,this)})));function t(t){return e.apply(this,arguments)}return t}()}}),s=o,l=(r("0f6e"),r("2877")),u=Object(l["a"])(s,a,n,!1,null,null,null);t["default"]=u.exports}}]);
|
1
frontend/dist/js/chunk-46dcb584.5ee0f4ea.js
vendored
1
frontend/dist/js/chunk-46dcb584.5ee0f4ea.js
vendored
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-46dcb584"],{"0125":function(e,t,r){var a=r("24fb");t=a(!1),t.push([e.i,".project-title{margin:50px}.project-title h1{font-size:50px;font-weight:100;text-align:center}.login-form{max-width:500px;margin:0 auto}footer{padding:30px;text-align:center}",""]),e.exports=t},"0f6e":function(e,t,r){"use strict";r("76f8")},"76f8":function(e,t,r){var a=r("0125");a.__esModule&&(a=a.default),"string"===typeof a&&(a=[[e.i,a,""]]),a.locals&&(e.exports=a.locals);var n=r("499e").default;n("447bb34c",a,!0,{sourceMap:!1,shadowMode:!1})},c756:function(e,t,r){"use strict";r.r(t);var a=function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"login-form"},[e._m(0),r("a-form",{staticClass:"login-form",attrs:{id:"components-form-demo-normal-login",form:e.form},on:{submit:e.handleSubmit}},[r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["email",{rules:[{type:"email",message:"The input is not valid E-mail!"},{required:!0,message:"Please input your E-mail!"}]}],expression:"[\n 'email',\n { rules: [{\n type: 'email',\n message: 'The input is not valid E-mail!',\n },\n {\n required: true,\n message: 'Please input your E-mail!',\n },] },\n ]"}],attrs:{placeholder:"Email"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"mail"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["username",{rules:[{required:!0,message:"Please input your username!"}]}],expression:"[\n 'username',\n { rules: [{ required: true, message: 'Please input your username!' }] },\n ]"}],attrs:{placeholder:"Username"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"user"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-input",{directives:[{name:"decorator",rawName:"v-decorator",value:["password",{rules:[{required:!0,message:"Please input your Password!"}]}],expression:"[\n 'password',\n { rules: [{ required: true, message: 'Please input your Password!' }] },\n ]"}],attrs:{type:"password",placeholder:"Password"}},[r("a-icon",{staticStyle:{color:"rgba(0,0,0,.25)"},attrs:{slot:"prefix",type:"lock"},slot:"prefix"})],1)],1),r("a-form-item",[r("a-button",{attrs:{type:"primary",block:!0,"html-type":"submit",loading:e.loading}},[e._v(" 安装 ")])],1)],1),r("footer",[e._v(" Copyright © 2020 - "+e._s(e.thisYear)+" 0xJacky ")])],1)},n=[function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("div",{staticClass:"project-title"},[r("h1",[e._v("Nginx UI")])])}],i=r("1da1"),o=(r("96cf"),{name:"Login",data:function(){return{form:{},lock:!0,thisYear:(new Date).getFullYear(),loading:!1}},created:function(){this.form=this.$form.createForm(this)},mounted:function(){var e=this;this.$api.install.get_lock().then((function(t){t.lock&&e.$router.push("/login")}))},methods:{handleSubmit:function(){var e=Object(i["a"])(regeneratorRuntime.mark((function e(t){var r=this;return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:return t.preventDefault(),this.loading=!0,e.next=4,this.form.validateFields(function(){var e=Object(i["a"])(regeneratorRuntime.mark((function e(t,a){return regeneratorRuntime.wrap((function(e){while(1)switch(e.prev=e.next){case 0:t||r.$api.install.install_nginx_ui(a).then((function(){r.$router.push("/login")})),r.loading=!1;case 2:case"end":return e.stop()}}),e)})));return function(t,r){return e.apply(this,arguments)}}());case 4:case"end":return e.stop()}}),e,this)})));function t(t){return e.apply(this,arguments)}return t}()}}),s=o,l=(r("0f6e"),r("2877")),u=Object(l["a"])(s,a,n,!1,null,null,null);t["default"]=u.exports}}]);
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-4f82bf3d.8d3be338.js
vendored
1
frontend/dist/js/chunk-4f82bf3d.8d3be338.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-5573b71a.92b99af4.js
vendored
1
frontend/dist/js/chunk-5573b71a.92b99af4.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
frontend/dist/js/chunk-5d4d188e.b0ffa164.js
vendored
8
frontend/dist/js/chunk-5d4d188e.b0ffa164.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-6a4ca29d"],{"06de":function(e,t,a){"use strict";a.r(t);var n=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("a-card",{staticStyle:{"text-align":"center"}},[a("div",{staticClass:"logo"},[a("img",{attrs:{src:e.logo,alt:"logo"}})]),a("h2",[e._v("Nginx UI")]),a("p",[e._v("Yet another WebUI for Nginx")]),a("p",[e._v("Version: "+e._s(e.version)+" ("+e._s(e.build_id)+")")]),a("h3",[e._v("项目组")]),a("p",[e._v("Designer:"),a("a",{attrs:{href:"https://jackyu.cn/"}},[e._v("@0xJacky")])]),a("h3",[e._v("技术栈")]),a("p",[e._v("Go")]),a("p",[e._v("Gin")]),a("p",[e._v("Vue")]),a("p",[e._v("Websocket")]),a("h3",[e._v("开源协议")]),a("p",[e._v("GNU General Public License v2.0")]),a("p",[e._v("Copyright © 2020 - "+e._s(e.this_year)+" 0xJacky ")])])},r=[],o=a("1da1"),s=(a("96cf"),{name:"About",data:function(){var e,t=new Date;return{logo:a("4ffd"),this_year:t.getFullYear(),version:"1.1.0",build_id:null!==(e="19")&&void 0!==e?e:"开发模式",api_root:"/api"}},methods:{changeUserPower:function(e){var t=this;return Object(o["a"])(regeneratorRuntime.mark((function a(){return regeneratorRuntime.wrap((function(a){while(1)switch(a.prev=a.next){case 0:return a.next=2,t.$store.dispatch("update_mock_user",{power:e});case 2:return a.next=4,t.$api.user.info();case 4:return a.next=6,t.$message.success("修改成功");case 6:case"end":return a.stop()}}),a)})))()}}}),c=s,i=(a("a695"),a("2877")),u=Object(i["a"])(c,n,r,!1,null,"bb72cb78",null);t["default"]=u.exports},"0c2d":function(e,t,a){var n=a("24fb");t=n(!1),t.push([e.i,".logo img[data-v-bb72cb78]{max-width:120px}.egg[data-v-bb72cb78]{padding:10px 0}.ant-btn[data-v-bb72cb78]{margin:10px 10px 0 0}",""]),e.exports=t},"4ffd":function(e,t,a){e.exports=a.p+"img/logo.9e691c6b.png"},5683:function(e,t,a){var n=a("0c2d");n.__esModule&&(n=n.default),"string"===typeof n&&(n=[[e.i,n,""]]),n.locals&&(e.exports=n.locals);var r=a("499e").default;r("fc536a1c",n,!0,{sourceMap:!1,shadowMode:!1})},a695:function(e,t,a){"use strict";a("5683")}}]);
|
1
frontend/dist/js/chunk-6a4ca29d.5593b7e1.js
vendored
1
frontend/dist/js/chunk-6a4ca29d.5593b7e1.js
vendored
|
@ -1 +0,0 @@
|
|||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-6a4ca29d"],{"06de":function(e,t,a){"use strict";a.r(t);var n=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("a-card",{staticStyle:{"text-align":"center"}},[a("div",{staticClass:"logo"},[a("img",{attrs:{src:e.logo,alt:"logo"}})]),a("h2",[e._v("Nginx UI")]),a("p",[e._v("Yet another WebUI for Nginx")]),a("p",[e._v("Version: "+e._s(e.version)+" ("+e._s(e.build_id)+")")]),a("h3",[e._v("项目组")]),a("p",[e._v("Designer:"),a("a",{attrs:{href:"https://jackyu.cn/"}},[e._v("@0xJacky")])]),a("h3",[e._v("技术栈")]),a("p",[e._v("Go")]),a("p",[e._v("Gin")]),a("p",[e._v("Vue")]),a("p",[e._v("Websocket")]),a("h3",[e._v("开源协议")]),a("p",[e._v("GNU General Public License v2.0")]),a("p",[e._v("Copyright © 2020 - "+e._s(e.this_year)+" 0xJacky ")])])},r=[],o=a("1da1"),s=(a("96cf"),{name:"About",data:function(){var e,t=new Date;return{logo:a("4ffd"),this_year:t.getFullYear(),version:"1.1.0",build_id:null!==(e="19")&&void 0!==e?e:"开发模式",api_root:"/api"}},methods:{changeUserPower:function(e){var t=this;return Object(o["a"])(regeneratorRuntime.mark((function a(){return regeneratorRuntime.wrap((function(a){while(1)switch(a.prev=a.next){case 0:return a.next=2,t.$store.dispatch("update_mock_user",{power:e});case 2:return a.next=4,t.$api.user.info();case 4:return a.next=6,t.$message.success("修改成功");case 6:case"end":return a.stop()}}),a)})))()}}}),c=s,i=(a("a695"),a("2877")),u=Object(i["a"])(c,n,r,!1,null,"bb72cb78",null);t["default"]=u.exports},"0c2d":function(e,t,a){var n=a("24fb");t=n(!1),t.push([e.i,".logo img[data-v-bb72cb78]{max-width:120px}.egg[data-v-bb72cb78]{padding:10px 0}.ant-btn[data-v-bb72cb78]{margin:10px 10px 0 0}",""]),e.exports=t},"4ffd":function(e,t,a){e.exports=a.p+"img/logo.9e691c6b.png"},5683:function(e,t,a){var n=a("0c2d");n.__esModule&&(n=n.default),"string"===typeof n&&(n=[[e.i,n,""]]),n.locals&&(e.exports=n.locals);var r=a("499e").default;r("fc536a1c",n,!0,{sourceMap:!1,shadowMode:!1})},a695:function(e,t,a){"use strict";a("5683")}}]);
|
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-83d83096.72980dc3.js
vendored
1
frontend/dist/js/chunk-83d83096.72980dc3.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-b508de6a.b421b1eb.js
vendored
1
frontend/dist/js/chunk-b508de6a.b421b1eb.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/dist/js/chunk-c8c0a686.85e5c7a1.js
vendored
1
frontend/dist/js/chunk-c8c0a686.85e5c7a1.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
39
frontend/dist/js/chunk-vendors.731e48fc.js
vendored
39
frontend/dist/js/chunk-vendors.731e48fc.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/js/index-legacy.ec1dfd27.js
vendored
1
frontend/dist/js/index-legacy.ec1dfd27.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/js/index.62a46eff.js
vendored
1
frontend/dist/js/index.62a46eff.js
vendored
File diff suppressed because one or more lines are too long
1
frontend/dist/version.json
vendored
1
frontend/dist/version.json
vendored
|
@ -1 +0,0 @@
|
|||
{"version":"1.1.0","build_id":2,"total_build":19}
|
6
frontend/frontend.go
Normal file
6
frontend/frontend.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package frontend
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed dist
|
||||
var DistFS embed.FS
|
|
@ -1 +1 @@
|
|||
{"version":"1.1.0","build_id":2,"total_build":19}
|
||||
{"version":"1.1.0","build_id":3,"total_build":20}
|
|
@ -1,13 +1,14 @@
|
|||
module github.com/0xJacky/Nginx-UI/server
|
||||
module github.com/0xJacky/Nginx-UI
|
||||
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/gin-contrib/static v0.0.1
|
||||
github.com/gin-gonic/gin v1.7.4
|
||||
github.com/go-acme/lego/v4 v4.4.0
|
||||
github.com/go-playground/locales v0.13.0 // indirect
|
||||
github.com/go-playground/locales v0.13.0
|
||||
github.com/go-playground/universal-translator v0.17.0
|
||||
github.com/go-playground/validator/v10 v10.4.1
|
||||
github.com/google/uuid v1.1.1
|
|
@ -107,6 +107,9 @@ github.com/getkin/kin-openapi v0.13.0/go.mod h1:WGRs2ZMM1Q8LR1QBEwUxC6RJEfaBcD0s
|
|||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
|
||||
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/go-acme/lego/v4 v4.4.0 h1:uHhU5LpOYQOdp3aDU+XY2bajseu8fuExphTL1Ss6/Fc=
|
||||
|
@ -128,6 +131,7 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c
|
|||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
|
32
main.go
Normal file
32
main.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/0xJacky/Nginx-UI/router"
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"github.com/0xJacky/Nginx-UI/tool"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var dataDir string
|
||||
flag.StringVar(&dataDir, "d", ".", "Specify the data dir")
|
||||
flag.Parse()
|
||||
|
||||
settings.Init(dataDir)
|
||||
model.Init()
|
||||
|
||||
r := router.InitRouter()
|
||||
|
||||
log.Printf("nginx config dir path: %s", tool.GetNginxConfPath(""))
|
||||
|
||||
go tool.AutoCert()
|
||||
|
||||
err := r.Run(":" + settings.ServerSettings.HttpPort)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
63
model/auth.go
Normal file
63
model/auth.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
Model
|
||||
|
||||
Name string `json:"name"`
|
||||
Password string `json:"-"`
|
||||
}
|
||||
|
||||
type AuthToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type JWTClaims struct {
|
||||
Name string `json:"name"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func GetUser(name string) (user Auth, err error) {
|
||||
err = db.Where("name = ?", name).First(&user).Error
|
||||
if err != nil {
|
||||
return Auth{}, err
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
func DeleteToken(token string) error {
|
||||
return db.Where("token = ?", token).Delete(&AuthToken{}).Error
|
||||
}
|
||||
|
||||
func CheckToken(token string) int64 {
|
||||
return db.Where("token = ?", token).Find(&AuthToken{}).RowsAffected
|
||||
}
|
||||
|
||||
func GenerateJWT(name string) (string, error) {
|
||||
claims := JWTClaims{
|
||||
Name: name,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
||||
},
|
||||
}
|
||||
unsignedToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
signedToken, err := unsignedToken.SignedString([]byte(settings.ServerSettings.JwtSecret))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = db.Create(&AuthToken{
|
||||
Token: signedToken,
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return signedToken, err
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
134
router/routers.go
Normal file
134
router/routers.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"github.com/0xJacky/Nginx-UI/api"
|
||||
"github.com/0xJacky/Nginx-UI/frontend"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/gin-contrib/static"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func authRequired() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
tmp, _ := base64.StdEncoding.DecodeString(c.Query("token"))
|
||||
token = string(tmp)
|
||||
if token == "" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"message": "auth fail",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
n := model.CheckToken(token)
|
||||
|
||||
if n < 1 {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"message": "auth fail",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
type serverFileSystemType struct {
|
||||
http.FileSystem
|
||||
}
|
||||
|
||||
func (f serverFileSystemType) Exists(prefix string, path string) bool {
|
||||
_, err := f.Open(filepath.Join(prefix, path))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func mustFS(dir string) (serverFileSystem static.ServeFileSystem) {
|
||||
|
||||
sub, err := fs.Sub(frontend.DistFS, filepath.Join("public", dir))
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
serverFileSystem = serverFileSystemType{
|
||||
http.FS(sub),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func InitRouter() *gin.Engine {
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger())
|
||||
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
r.Use(static.Serve("/", mustFS("")))
|
||||
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
accept := c.Request.Header.Get("Accept")
|
||||
if strings.Contains(accept, "text/html") {
|
||||
file, _ := mustFS("").Open("index.html")
|
||||
stat, _ := file.Stat()
|
||||
c.DataFromReader(http.StatusOK, stat.Size(), "text/html",
|
||||
bufio.NewReader(file), nil)
|
||||
}
|
||||
})
|
||||
|
||||
g := r.Group("/api")
|
||||
{
|
||||
g.GET("install", api.InstallLockCheck)
|
||||
g.POST("install", api.InstallNginxUI)
|
||||
|
||||
g.POST("/login", api.Login)
|
||||
g.DELETE("/logout", api.Logout)
|
||||
|
||||
g := g.Group("/", authRequired())
|
||||
{
|
||||
g.GET("/analytic", api.Analytic)
|
||||
|
||||
g.GET("/users", api.GetUsers)
|
||||
g.GET("/user/:id", api.GetUser)
|
||||
g.POST("/user", api.AddUser)
|
||||
g.POST("/user/:id", api.EditUser)
|
||||
g.DELETE("/user/:id", api.DeleteUser)
|
||||
|
||||
g.GET("domains", api.GetDomains)
|
||||
g.GET("domain/:name", api.GetDomain)
|
||||
g.POST("domain/:name", api.EditDomain)
|
||||
g.POST("domain/:name/enable", api.EnableDomain)
|
||||
g.POST("domain/:name/disable", api.DisableDomain)
|
||||
g.DELETE("domain/:name", api.DeleteDomain)
|
||||
|
||||
g.GET("configs", api.GetConfigs)
|
||||
g.GET("config/:name", api.GetConfig)
|
||||
g.POST("config", api.AddConfig)
|
||||
g.POST("config/:name", api.EditConfig)
|
||||
|
||||
g.GET("backups", api.GetFileBackupList)
|
||||
g.GET("backup/:id", api.GetFileBackup)
|
||||
|
||||
g.GET("template/:name", api.GetTemplate)
|
||||
|
||||
g.GET("cert/issue/:domain", api.IssueCert)
|
||||
g.GET("cert/:domain/info", api.CertInfo)
|
||||
|
||||
// 添加域名到自动续期列表
|
||||
g.POST("cert/:domain", api.AddDomainToAutoCert)
|
||||
// 从自动续期列表中删除域名
|
||||
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
|
||||
}
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type LoginUser struct {
|
||||
Name string `json:"name" binding:"required,max=255"`
|
||||
Password string `json:"password" binding:"required,max=255"`
|
||||
}
|
||||
|
||||
func Login(c *gin.Context) {
|
||||
var user LoginUser
|
||||
ok := BindAndValid(c, &user)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
u, _ := model.GetUser(user.Name)
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(user.Password)); err != nil {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"message": "用户名或密码错误",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
token, err := model.GenerateJWT(u.Name)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
"token": token,
|
||||
})
|
||||
}
|
||||
|
||||
func Logout(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token != "" {
|
||||
err := model.DeleteToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
c.JSON(http.StatusNoContent, nil)
|
||||
}
|
|
@ -1,263 +0,0 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func GetDomains(c *gin.Context) {
|
||||
orderBy := c.Query("order_by")
|
||||
sort := c.DefaultQuery("sort", "desc")
|
||||
|
||||
mySort := map[string]string{
|
||||
"enabled": "bool",
|
||||
"name": "string",
|
||||
"modify": "time",
|
||||
}
|
||||
|
||||
configFiles, err := ioutil.ReadDir(tool.GetNginxConfPath("sites-available"))
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
enabledConfig, err := ioutil.ReadDir(filepath.Join(tool.GetNginxConfPath("sites-enabled")))
|
||||
|
||||
enabledConfigMap := make(map[string]bool)
|
||||
for i := range enabledConfig {
|
||||
enabledConfigMap[enabledConfig[i].Name()] = true
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var configs []gin.H
|
||||
|
||||
for i := range configFiles {
|
||||
file := configFiles[i]
|
||||
if !file.IsDir() {
|
||||
configs = append(configs, gin.H{
|
||||
"name": file.Name(),
|
||||
"size": file.Size(),
|
||||
"modify": file.ModTime(),
|
||||
"enabled": enabledConfigMap[file.Name()],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
configs = tool.Sort(orderBy, sort, mySort[orderBy], configs)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"configs": configs,
|
||||
})
|
||||
}
|
||||
|
||||
func GetDomain(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
||||
|
||||
enabled := true
|
||||
if _, err := os.Stat(filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)); os.IsNotExist(err) {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(path)
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = model.FirstCert(name)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"enabled": enabled,
|
||||
"name": name,
|
||||
"config": string(content),
|
||||
"auto_cert": err == nil,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func EditDomain(c *gin.Context) {
|
||||
var err error
|
||||
name := c.Param("name")
|
||||
request := make(gin.H)
|
||||
err = c.BindJSON(&request)
|
||||
path := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
||||
|
||||
err = ioutil.WriteFile(path, []byte(request["content"].(string)), 0644)
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
|
||||
if _, err = os.Stat(enabledConfigFilePath); err == nil {
|
||||
// 测试配置文件
|
||||
err = tool.TestNginxConf(enabledConfigFilePath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
output := tool.ReloadNginx()
|
||||
|
||||
if output != "" {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
GetDomain(c)
|
||||
}
|
||||
|
||||
func EnableDomain(c *gin.Context) {
|
||||
configFilePath := filepath.Join(tool.GetNginxConfPath("sites-available"), c.Param("name"))
|
||||
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||
|
||||
_, err := os.Stat(configFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Symlink(configFilePath, enabledConfigFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 测试配置文件,不通过则撤回启用
|
||||
err = tool.TestNginxConf(enabledConfigFilePath)
|
||||
if err != nil {
|
||||
_ = os.Remove(enabledConfigFilePath)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
output := tool.ReloadNginx()
|
||||
|
||||
if output != "" {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func DisableDomain(c *gin.Context) {
|
||||
enabledConfigFilePath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), c.Param("name"))
|
||||
|
||||
_, err := os.Stat(enabledConfigFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.Remove(enabledConfigFilePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
output := tool.ReloadNginx()
|
||||
|
||||
if output != "" {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"message": output,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteDomain(c *gin.Context) {
|
||||
var err error
|
||||
name := c.Param("name")
|
||||
availablePath := filepath.Join(tool.GetNginxConfPath("sites-available"), name)
|
||||
enabledPath := filepath.Join(tool.GetNginxConfPath("sites-enabled"), name)
|
||||
|
||||
if _, err = os.Stat(availablePath); os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": "site not found",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = os.Stat(enabledPath); err == nil {
|
||||
c.JSON(http.StatusNotAcceptable, gin.H{
|
||||
"message": "site is enabled",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cert := model.Cert{Domain: name}
|
||||
_ = cert.Remove()
|
||||
|
||||
err = os.Remove(availablePath)
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func AddDomainToAutoCert(c *gin.Context) {
|
||||
domain := c.Param("domain")
|
||||
cert, err := model.FirstOrCreateCert(domain)
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, cert)
|
||||
}
|
||||
|
||||
func RemoveDomainFromAutoCert(c *gin.Context) {
|
||||
cert := model.Cert{
|
||||
Domain: c.Param("domain"),
|
||||
}
|
||||
err := cert.Remove()
|
||||
|
||||
if err != nil {
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, nil)
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/gin-gonic/gin"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetTemplate(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
path := filepath.Join("template", name)
|
||||
content, err := ioutil.ReadFile(path)
|
||||
|
||||
_content := string(content)
|
||||
_content = strings.ReplaceAll(_content, "{{ HTTP01PORT }}",
|
||||
settings.ServerSettings.HTTPChallengePort)
|
||||
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.JSON(http.StatusNotFound, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
ErrHandler(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "ok",
|
||||
"template": _content,
|
||||
})
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/router"
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var dataDir string
|
||||
flag.StringVar(&dataDir, "d", ".", "Specify the data dir")
|
||||
flag.Parse()
|
||||
|
||||
settings.Init(dataDir)
|
||||
model.Init()
|
||||
|
||||
r := router.InitRouter()
|
||||
|
||||
log.Printf("nginx config dir path: %s", tool.GetNginxConfPath(""))
|
||||
|
||||
go tool.AutoCert()
|
||||
|
||||
err := r.Run(":" + settings.ServerSettings.HttpPort)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Auth struct {
|
||||
Model
|
||||
|
||||
Name string `json:"name"`
|
||||
Password string `json:"-"`
|
||||
}
|
||||
|
||||
type AuthToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type JWTClaims struct {
|
||||
Name string `json:"name"`
|
||||
jwt.StandardClaims
|
||||
}
|
||||
|
||||
func GetUser(name string) (user Auth, err error){
|
||||
err = db.Where("name = ?", name).First(&user).Error
|
||||
if err != nil {
|
||||
return Auth{}, err
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
func DeleteToken(token string) error {
|
||||
return db.Where("token = ?", token).Delete(&AuthToken{}).Error
|
||||
}
|
||||
|
||||
func CheckToken(token string) int64 {
|
||||
return db.Where("token = ?", token).Find(&AuthToken{}).RowsAffected
|
||||
}
|
||||
|
||||
func GenerateJWT(name string) (string, error) {
|
||||
claims := JWTClaims{
|
||||
Name: name,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
|
||||
},
|
||||
}
|
||||
unsignedToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
signedToken, err := unsignedToken.SignedString([]byte(settings.ServerSettings.JwtSecret))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = db.Create(&AuthToken{
|
||||
Token: signedToken,
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return signedToken, err
|
||||
}
|
|
@ -1,94 +0,0 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/0xJacky/Nginx-UI/server/api"
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func authRequired() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.GetHeader("Authorization")
|
||||
if token == "" {
|
||||
tmp, _ := base64.StdEncoding.DecodeString(c.Query("token"))
|
||||
token = string(tmp)
|
||||
if token == "" {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"message": "auth fail",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
n := model.CheckToken(token)
|
||||
|
||||
if n < 1 {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"message": "auth fail",
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func InitRouter() *gin.Engine {
|
||||
r := gin.New()
|
||||
r.Use(gin.Logger())
|
||||
|
||||
r.Use(gin.Recovery())
|
||||
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Hello World",
|
||||
})
|
||||
})
|
||||
|
||||
r.GET("install", api.InstallLockCheck)
|
||||
r.POST("install", api.InstallNginxUI)
|
||||
|
||||
r.POST("/login", api.Login)
|
||||
r.DELETE("/logout", api.Logout)
|
||||
|
||||
g := r.Group("/", authRequired())
|
||||
{
|
||||
r.GET("/analytic", api.Analytic)
|
||||
|
||||
g.GET("/users", api.GetUsers)
|
||||
g.GET("/user/:id", api.GetUser)
|
||||
g.POST("/user", api.AddUser)
|
||||
g.POST("/user/:id", api.EditUser)
|
||||
g.DELETE("/user/:id", api.DeleteUser)
|
||||
|
||||
g.GET("domains", api.GetDomains)
|
||||
g.GET("domain/:name", api.GetDomain)
|
||||
g.POST("domain/:name", api.EditDomain)
|
||||
g.POST("domain/:name/enable", api.EnableDomain)
|
||||
g.POST("domain/:name/disable", api.DisableDomain)
|
||||
g.DELETE("domain/:name", api.DeleteDomain)
|
||||
|
||||
g.GET("configs", api.GetConfigs)
|
||||
g.GET("config/:name", api.GetConfig)
|
||||
g.POST("config", api.AddConfig)
|
||||
g.POST("config/:name", api.EditConfig)
|
||||
|
||||
g.GET("backups", api.GetFileBackupList)
|
||||
g.GET("backup/:id", api.GetFileBackup)
|
||||
|
||||
g.GET("template/:name", api.GetTemplate)
|
||||
|
||||
g.GET("cert/issue/:domain", api.IssueCert)
|
||||
g.GET("cert/:domain/info", api.CertInfo)
|
||||
|
||||
// 添加域名到自动续期列表
|
||||
g.POST("cert/:domain", api.AddDomainToAutoCert)
|
||||
// 从自动续期列表中删除域名
|
||||
g.DELETE("cert/:domain", api.RemoveDomainFromAutoCert)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAcme(t *testing.T) {
|
||||
const acmePath = "/usr/local/acme.sh"
|
||||
_, err := os.Stat(acmePath)
|
||||
log.Println("[found] acme.sh ", acmePath)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
if os.IsNotExist(err) {
|
||||
log.Println("[not found] acme.sh, installing...")
|
||||
|
||||
if _, err := os.Stat("../tmp"); os.IsNotExist(err) {
|
||||
_ = os.Mkdir("../tmp", 0644)
|
||||
}
|
||||
|
||||
out, err := exec.Command("curl", "-o", "../tmp/acme.sh", "https://get.acme.sh").
|
||||
CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n", out)
|
||||
|
||||
log.Println("[acme.sh] downloaded")
|
||||
|
||||
file, _ := ioutil.ReadFile("../tmp/acme.sh")
|
||||
|
||||
fileString := string(file)
|
||||
fileString = strings.Replace(fileString, "https://raw.githubusercontent.com",
|
||||
"https://ghproxy.com/https://raw.githubusercontent.com", -1)
|
||||
|
||||
_ = ioutil.WriteFile("../tmp/acme.sh", []byte(fileString), 0644)
|
||||
|
||||
out, err = exec.Command("bash", "../tmp/acme.sh",
|
||||
"install",
|
||||
"--log",
|
||||
"--home", "/usr/local/acme.sh",
|
||||
"--cert-home", tool.GetNginxConfPath("ssl")).
|
||||
CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n", out)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/0xJacky/Nginx-UI/server/tool"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCert(t *testing.T) {
|
||||
out, err := exec.Command("bash", "/usr/local/acme.sh/acme.sh",
|
||||
"--issue",
|
||||
"-d", "test.ojbk.me",
|
||||
"--nginx").CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n", out)
|
||||
|
||||
_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/fullchain.cer"))
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
log.Println("[found]", "fullchain.cer")
|
||||
_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/test.ojbk.me.key"))
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("[found]", "cert key")
|
||||
|
||||
log.Println("申请成功")
|
||||
}
|
|
@ -1,169 +0,0 @@
|
|||
package tool
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"github.com/0xJacky/Nginx-UI/server/model"
|
||||
"github.com/0xJacky/Nginx-UI/server/settings"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MyUser You'll need a user or account type that implements acme.User
|
||||
type MyUser struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func (u *MyUser) GetEmail() string {
|
||||
return u.Email
|
||||
}
|
||||
func (u MyUser) GetRegistration() *registration.Resource {
|
||||
return u.Registration
|
||||
}
|
||||
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
||||
return u.key
|
||||
}
|
||||
|
||||
func AutoCert() {
|
||||
for {
|
||||
log.Println("[AutoCert] Start")
|
||||
autoCertList := model.GetAutoCertList()
|
||||
for i := range autoCertList {
|
||||
domain := autoCertList[i].Domain
|
||||
key := GetCertInfo(domain)
|
||||
// 未到一个月
|
||||
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
|
||||
continue
|
||||
}
|
||||
// 过一个月了
|
||||
err := IssueCert(domain)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
func GetCertInfo(domain string) (key *x509.Certificate) {
|
||||
ts := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: ts}
|
||||
|
||||
response, err := client.Get("https://" + domain)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err = Body.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}(response.Body)
|
||||
|
||||
key = response.TLS.PeerCertificates[0]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func IssueCert(domain string) error {
|
||||
// Create a user. New accounts need an email and private key to start.
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
myUser := MyUser{
|
||||
Email: settings.ServerSettings.Email,
|
||||
key: privateKey,
|
||||
}
|
||||
|
||||
config := lego.NewConfig(&myUser)
|
||||
|
||||
//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
|
||||
// A client facilitates communication with the CA server.
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = client.Challenge.SetHTTP01Provider(
|
||||
http01.NewProviderServer("",
|
||||
settings.ServerSettings.HTTPChallengePort,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// New users will need to register
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
myUser.Registration = reg
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: []string{domain},
|
||||
Bundle: true,
|
||||
}
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
saveDir := GetNginxConfPath("ssl/" + domain)
|
||||
if _, err := os.Stat(saveDir); os.IsNotExist(err) {
|
||||
err = os.Mkdir(saveDir, 0755)
|
||||
if err != nil {
|
||||
log.Println("fail to create", saveDir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||
// private key, and a certificate URL. SAVE THESE TO DISK.
|
||||
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
||||
certificates.Certificate, 0644)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
|
||||
certificates.PrivateKey, 0644)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
ReloadNginx()
|
||||
|
||||
return nil
|
||||
}
|
59
test/acme_test.go
Normal file
59
test/acme_test.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/0xJacky/Nginx-UI/tool"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAcme(t *testing.T) {
|
||||
const acmePath = "/usr/local/acme.sh"
|
||||
_, err := os.Stat(acmePath)
|
||||
log.Println("[found] acme.sh ", acmePath)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
if os.IsNotExist(err) {
|
||||
log.Println("[not found] acme.sh, installing...")
|
||||
|
||||
if _, err := os.Stat("../tmp"); os.IsNotExist(err) {
|
||||
_ = os.Mkdir("../tmp", 0644)
|
||||
}
|
||||
|
||||
out, err := exec.Command("curl", "-o", "../tmp/acme.sh", "https://get.acme.sh").
|
||||
CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n", out)
|
||||
|
||||
log.Println("[acme.sh] downloaded")
|
||||
|
||||
file, _ := ioutil.ReadFile("../tmp/acme.sh")
|
||||
|
||||
fileString := string(file)
|
||||
fileString = strings.Replace(fileString, "https://raw.githubusercontent.com",
|
||||
"https://ghproxy.com/https://raw.githubusercontent.com", -1)
|
||||
|
||||
_ = ioutil.WriteFile("../tmp/acme.sh", []byte(fileString), 0644)
|
||||
|
||||
out, err = exec.Command("bash", "../tmp/acme.sh",
|
||||
"install",
|
||||
"--log",
|
||||
"--home", "/usr/local/acme.sh",
|
||||
"--cert-home", tool.GetNginxConfPath("ssl")).
|
||||
CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n", out)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
40
test/cert_test.go
Normal file
40
test/cert_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/0xJacky/Nginx-UI/tool"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCert(t *testing.T) {
|
||||
out, err := exec.Command("bash", "/usr/local/acme.sh/acme.sh",
|
||||
"--issue",
|
||||
"-d", "test.ojbk.me",
|
||||
"--nginx").CombinedOutput()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%s\n", out)
|
||||
|
||||
_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/fullchain.cer"))
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
log.Println("[found]", "fullchain.cer")
|
||||
_, err = os.Stat(tool.GetNginxConfPath("ssl/test.ojbk.me/test.ojbk.me.key"))
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("[found]", "cert key")
|
||||
|
||||
log.Println("申请成功")
|
||||
}
|
169
tool/cert.go
Normal file
169
tool/cert.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
package tool
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"github.com/0xJacky/Nginx-UI/model"
|
||||
"github.com/0xJacky/Nginx-UI/settings"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge/http01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MyUser You'll need a user or account type that implements acme.User
|
||||
type MyUser struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func (u *MyUser) GetEmail() string {
|
||||
return u.Email
|
||||
}
|
||||
func (u MyUser) GetRegistration() *registration.Resource {
|
||||
return u.Registration
|
||||
}
|
||||
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
||||
return u.key
|
||||
}
|
||||
|
||||
func AutoCert() {
|
||||
for {
|
||||
log.Println("[AutoCert] Start")
|
||||
autoCertList := model.GetAutoCertList()
|
||||
for i := range autoCertList {
|
||||
domain := autoCertList[i].Domain
|
||||
key := GetCertInfo(domain)
|
||||
// 未到一个月
|
||||
if time.Now().Before(key.NotBefore.AddDate(0, 1, 0)) {
|
||||
continue
|
||||
}
|
||||
// 过一个月了
|
||||
err := IssueCert(domain)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
func GetCertInfo(domain string) (key *x509.Certificate) {
|
||||
ts := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: ts}
|
||||
|
||||
response, err := client.Get("https://" + domain)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func(Body io.ReadCloser) {
|
||||
err = Body.Close()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}(response.Body)
|
||||
|
||||
key = response.TLS.PeerCertificates[0]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func IssueCert(domain string) error {
|
||||
// Create a user. New accounts need an email and private key to start.
|
||||
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
myUser := MyUser{
|
||||
Email: settings.ServerSettings.Email,
|
||||
key: privateKey,
|
||||
}
|
||||
|
||||
config := lego.NewConfig(&myUser)
|
||||
|
||||
//config.CADirURL = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
config.Certificate.KeyType = certcrypto.RSA2048
|
||||
|
||||
// A client facilitates communication with the CA server.
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
err = client.Challenge.SetHTTP01Provider(
|
||||
http01.NewProviderServer("",
|
||||
settings.ServerSettings.HTTPChallengePort,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// New users will need to register
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
myUser.Registration = reg
|
||||
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: []string{domain},
|
||||
Bundle: true,
|
||||
}
|
||||
certificates, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
saveDir := GetNginxConfPath("ssl/" + domain)
|
||||
if _, err := os.Stat(saveDir); os.IsNotExist(err) {
|
||||
err = os.Mkdir(saveDir, 0755)
|
||||
if err != nil {
|
||||
log.Println("fail to create", saveDir)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||
// private key, and a certificate URL. SAVE THESE TO DISK.
|
||||
err = ioutil.WriteFile(filepath.Join(saveDir, "fullchain.cer"),
|
||||
certificates.Certificate, 0644)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(filepath.Join(saveDir, domain+".key"),
|
||||
certificates.PrivateKey, 0644)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
ReloadNginx()
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue