Bläddra i källkod

feat: added restart nginx btn

0xJacky 2 år sedan
förälder
incheckning
c9c83c9db9

+ 3 - 0
frontend/components.d.ts

@@ -8,6 +8,7 @@ export {}
 declare module '@vue/runtime-core' {
   export interface GlobalComponents {
     AAlert: typeof import('ant-design-vue/es')['Alert']
+    ABadge: typeof import('ant-design-vue/es')['Badge']
     ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
     ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
     AButton: typeof import('ant-design-vue/es')['Button']
@@ -37,6 +38,7 @@ declare module '@vue/runtime-core' {
     AModal: typeof import('ant-design-vue/es')['Modal']
     APagination: typeof import('ant-design-vue/es')['Pagination']
     APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
+    APopover: typeof import('ant-design-vue/es')['Popover']
     AProgress: typeof import('ant-design-vue/es')['Progress']
     AResult: typeof import('ant-design-vue/es')['Result']
     ARow: typeof import('ant-design-vue/es')['Row']
@@ -59,6 +61,7 @@ declare module '@vue/runtime-core' {
     CodeEditorCodeEditor: typeof import('./src/components/CodeEditor/CodeEditor.vue')['default']
     FooterToolbarFooterToolBar: typeof import('./src/components/FooterToolbar/FooterToolBar.vue')['default']
     LogoLogo: typeof import('./src/components/Logo/Logo.vue')['default']
+    NginxControlNginxControl: typeof import('./src/components/NginxControl/NginxControl.vue')['default']
     PageHeaderPageHeader: typeof import('./src/components/PageHeader/PageHeader.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 3 - 3
frontend/package.json

@@ -32,9 +32,9 @@
         "vue3-apexcharts": "^1.4.1",
         "vue3-gettext": "^2.3.4",
         "vuedraggable": "^4.1.0",
-        "xterm": "^5.0.0",
-        "xterm-addon-attach": "^0.7.0",
-        "xterm-addon-fit": "^0.6.0"
+        "xterm": "^5.1.0",
+        "xterm-addon-attach": "^0.8.0",
+        "xterm-addon-fit": "^0.7.0"
     },
     "devDependencies": {
         "@vitejs/plugin-vue": "^4.0.0",

+ 8 - 0
frontend/src/api/ngx.ts

@@ -13,10 +13,18 @@ const ngx = {
         return http.post('/ngx/format_code', {content})
     },
 
+    status() {
+        return http.get('/nginx/status')
+    },
+
     reload() {
         return http.post('/nginx/reload')
     },
 
+    restart() {
+        return http.post('/nginx/restart')
+    },
+
     test() {
         return http.post('/nginx/test')
     }

+ 110 - 0
frontend/src/components/NginxControl/NginxControl.vue

@@ -0,0 +1,110 @@
+<script setup lang="ts">
+import gettext from '@/gettext'
+
+const {$gettext} = gettext
+import ngx from '@/api/ngx'
+import logLevel from '@/views/config/constants'
+import {message} from 'ant-design-vue'
+import {ReloadOutlined} from '@ant-design/icons-vue'
+import Template from '@/views/template/Template.vue'
+import {ref, watch} from 'vue'
+
+function get_status() {
+    ngx.status().then(r => {
+        if (r?.running === true) {
+            status.value = 0
+        } else {
+            status.value = -1
+        }
+    })
+}
+
+function reload_nginx() {
+    status.value = 1
+    ngx.reload().then(r => {
+        if (r.level < logLevel.Warn) {
+            message.success($gettext('Nginx reloaded successfully'))
+        } else if (r.level === logLevel.Warn) {
+            message.warn(r.message)
+        } else {
+            message.error(r.message)
+        }
+    }).catch(e => {
+        message.error($gettext('Server error') + ' ' + e?.message)
+    }).finally(() => {
+        status.value = 0
+    })
+}
+
+function restart_nginx() {
+    status.value = 2
+    ngx.restart().then(r => {
+        if (r.level < logLevel.Warn) {
+            message.success($gettext('Nginx restarted successfully'))
+        } else if (r.level === logLevel.Warn) {
+            message.warn(r.message)
+        } else {
+            message.error(r.message)
+        }
+    }).catch(e => {
+        message.error($gettext('Server error') + ' ' + e?.message)
+    }).finally(() => {
+        status.value = 0
+    })
+}
+
+const status = ref(0)
+
+const visible = ref(false)
+
+watch(visible, (v) => {
+    if (v) get_status()
+})
+</script>
+
+<template>
+    <a-popover
+        v-model:visible="visible"
+        @confirm="reload_nginx"
+        placement="bottomRight"
+    >
+        <template #content>
+            <div class="content-wrapper">
+                <h4>{{ $gettext('Nginx Control') }}</h4>
+                <a-badge v-if="status===0" color="green" :text="$gettext('Running')"/>
+                <a-badge v-else-if="status===1" color="blue" :text="$gettext('Reloading')"/>
+                <a-badge v-else-if="status===2" color="orange" :text="$gettext('Restarting')"/>
+                <a-badge v-else color="red" :text="$gettext('Stopped')"/>
+            </div>
+            <a-space>
+                <a-button size="small" @click="restart_nginx" type="link">{{ $gettext('Restart') }}</a-button>
+                <a-button size="small" @click="reload_nginx" type="link">{{ $gettext('Reload') }}</a-button>
+            </a-space>
+        </template>
+        <a>
+            <ReloadOutlined/>
+        </a>
+    </a-popover>
+</template>
+
+<style lang="less" scoped>
+a {
+    color: #000000;
+}
+
+.dark {
+    a {
+        color: #fafafa;
+    }
+}
+
+.content-wrapper {
+    text-align: center;
+    padding-top: 5px;
+    padding-bottom: 5px;
+
+    h4 {
+        margin-bottom: 5px;
+    }
+}
+</style>

+ 3 - 28
frontend/src/layouts/HeaderLayout.vue

@@ -3,10 +3,9 @@ import SetLanguage from '@/components/SetLanguage/SetLanguage.vue'
 import gettext from '@/gettext'
 import {message} from 'ant-design-vue'
 import auth from '@/api/auth'
-import {HomeOutlined, LogoutOutlined, MenuUnfoldOutlined, ReloadOutlined} from '@ant-design/icons-vue'
+import {HomeOutlined, LogoutOutlined, MenuUnfoldOutlined} from '@ant-design/icons-vue'
 import {useRouter} from 'vue-router'
-import ngx from '@/api/ngx'
-import logLevel from '@/views/config/constants'
+import NginxControl from '@/components/NginxControl/NginxControl.vue'
 
 const {$gettext} = gettext
 
@@ -19,20 +18,6 @@ function logout() {
         router.push('/login')
     })
 }
-
-function reload_nginx() {
-    ngx.reload().then(r => {
-        if (r.level < logLevel.Warn) {
-            message.success($gettext('Nginx reloaded successfully'))
-        } else if (r.level === logLevel.Warn) {
-            message.warn(r.message)
-        } else {
-            message.error(r.message)
-        }
-    }).catch(e => {
-        message.error($gettext('Server error') + ' ' + e?.message)
-    })
-}
 </script>
 
 <template>
@@ -48,17 +33,7 @@ function reload_nginx() {
                 <HomeOutlined/>
             </a>
 
-            <a-popconfirm
-                :title="$gettext('Do you want to reload Nginx?')"
-                :ok-text="$gettext('Yes')"
-                :cancel-text="$gettext('No')"
-                @confirm="reload_nginx"
-                placement="bottomRight"
-            >
-                <a>
-                    <ReloadOutlined/>
-                </a>
-            </a-popconfirm>
+            <NginxControl/>
 
             <a @click="logout">
                 <LogoutOutlined/>

+ 5 - 0
frontend/src/lib/http/index.ts

@@ -1,6 +1,8 @@
 import axios, {AxiosRequestConfig} from 'axios'
 import {useUserStore} from '@/pinia'
 import {storeToRefs} from 'pinia'
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
 
 import router from '@/routes'
 
@@ -25,6 +27,7 @@ let instance = axios.create({
 
 instance.interceptors.request.use(
     config => {
+        NProgress.start()
         if (token) {
             (config.headers as any).Authorization = token.value
         }
@@ -37,9 +40,11 @@ instance.interceptors.request.use(
 
 instance.interceptors.response.use(
     response => {
+        NProgress.done()
         return Promise.resolve(response.data)
     },
     async error => {
+        NProgress.done()
         switch (error.response.status) {
             case 401:
             case 403:

+ 1 - 1
frontend/src/version.json

@@ -1 +1 @@
-{"version":"1.7.5","build_id":77,"total_build":147}
+{"version":"1.7.5","build_id":81,"total_build":151}

+ 2 - 3
frontend/src/views/pty/Terminal.vue

@@ -35,14 +35,13 @@ const fit = _.throttle(function () {
 
 function initTerm() {
     term = new Terminal({
-        rendererType: 'canvas',
         convertEol: true,
         fontSize: 14,
         cursorStyle: 'block',
         scrollback: 1000,
         theme: {
-            background: '#000',
-        },
+            background: '#000'
+        }
     })
 
     term.loadAddon(fitAddon)

+ 1 - 1
frontend/version.json

@@ -1 +1 @@
-{"version":"1.7.5","build_id":77,"total_build":147}
+{"version":"1.7.5","build_id":81,"total_build":151}

+ 13 - 13
frontend/yarn.lock

@@ -3019,20 +3019,20 @@ wrappy@1:
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
 
-xterm-addon-attach@^0.7.0:
+xterm-addon-attach@^0.8.0:
+  version "0.8.0"
+  resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.8.0.tgz#86e0ed08460efffb7d4bad74a57b600226594def"
+  integrity sha512-k8N5boSYn6rMJTTNCgFpiSTZ26qnYJf3v/nJJYexNO2sdAHDN3m1ivVQWVZ8CHJKKnZQw1rc44YP2NtgalWHfQ==
+
+xterm-addon-fit@^0.7.0:
   version "0.7.0"
-  resolved "https://registry.yarnpkg.com/xterm-addon-attach/-/xterm-addon-attach-0.7.0.tgz#6775b3c5f1e9f91ae4cd9089f5ecb49fda8d2946"
-  integrity sha512-Yh3Kvq2e28onjnmGizQKZwlRMp9gmuZEHVX0BVZbo463YSjkqfhQS3T2wMLuO+j8AokccXsMa1z0bH1/+MMYuQ==
-
-xterm-addon-fit@^0.6.0:
-  version "0.6.0"
-  resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.6.0.tgz#142e1ce181da48763668332593fc440349c88c34"
-  integrity sha512-9/7A+1KEjkFam0yxTaHfuk9LEvvTSBi0PZmEkzJqgafXPEXL9pCMAVV7rB09sX6ATRDXAdBpQhZkhKj7CGvYeg==
-
-xterm@^5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0.tgz#0af50509b33d0dc62fde7a4ec17750b8e453cc5c"
-  integrity sha512-tmVsKzZovAYNDIaUinfz+VDclraQpPUnAME+JawosgWRMphInDded/PuY0xmU5dOhyeYZsI0nz5yd8dPYsdLTA==
+  resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz#b8ade6d96e63b47443862088f6670b49fb752c6a"
+  integrity sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ==
+
+xterm@^5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.1.0.tgz#3e160d60e6801c864b55adf19171c49d2ff2b4fc"
+  integrity sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ==
 
 yallist@^3.0.2:
   version "3.1.1"

+ 23 - 0
server/api/ngx.go

@@ -4,6 +4,7 @@ import (
     "github.com/0xJacky/Nginx-UI/server/pkg/nginx"
     "github.com/gin-gonic/gin"
     "net/http"
+    "os"
 )
 
 func BuildNginxConfig(c *gin.Context) {
@@ -48,6 +49,19 @@ func FormatNginxConfig(c *gin.Context) {
     })
 }
 
+func NginxStatus(c *gin.Context) {
+    pidPath := nginx.GetNginxPIDPath()
+
+    running := true
+    if _, err := os.Stat(pidPath); err != nil {
+        running = false
+    }
+
+    c.JSON(http.StatusOK, gin.H{
+        "running": running,
+    })
+}
+
 func ReloadNginx(c *gin.Context) {
     output := nginx.Reload()
 
@@ -65,3 +79,12 @@ func TestNginx(c *gin.Context) {
         "level":   nginx.GetLogLevel(output),
     })
 }
+
+func RestartNginx(c *gin.Context) {
+    output := nginx.Restart()
+
+    c.JSON(http.StatusOK, gin.H{
+        "message": output,
+        "level":   nginx.GetLogLevel(output),
+    })
+}

+ 33 - 1
server/pkg/nginx/nginx.go

@@ -27,8 +27,17 @@ func Reload() string {
 	return string(out)
 }
 
-func GetConfPath(dir ...string) string {
+func Restart() string {
+	out, err := exec.Command("nginx", "-s", "reopen").CombinedOutput()
+
+	if err != nil {
+		log.Println("[error] nginx.Restart", err)
+	}
+
+	return string(out)
+}
 
+func GetConfPath(dir ...string) string {
 	var confPath string
 
 	if settings.ServerSettings.NginxConfigDir == "" {
@@ -50,3 +59,26 @@ func GetConfPath(dir ...string) string {
 
 	return filepath.Join(confPath, filepath.Join(dir...))
 }
+
+func GetNginxPIDPath() string {
+	var confPath string
+
+	if settings.ServerSettings.NginxPIDPath == "" {
+		out, err := exec.Command("nginx", "-V").CombinedOutput()
+		if err != nil {
+			log.Println("nginx.GetNginxPIDPath exec.Command error", err)
+			return ""
+		}
+		r, _ := regexp.Compile("--pid-path=(.*.pid)")
+		match := r.FindStringSubmatch(string(out))
+		if len(match) < 1 {
+			log.Println("nginx.GetNginxPIDPath len(match) < 1")
+			return ""
+		}
+		confPath = r.FindStringSubmatch(string(out))[1]
+	} else {
+		confPath = settings.ServerSettings.NginxPIDPath
+	}
+
+	return confPath
+}

+ 4 - 0
server/router/routers.go

@@ -64,8 +64,12 @@ func InitRouter() *gin.Engine {
 			g.POST("ngx/format_code", api.FormatNginxConfig)
 			// nginx reload
 			g.POST("nginx/reload", api.ReloadNginx)
+			// nginx restart
+			g.POST("nginx/restart", api.RestartNginx)
 			// nginx test
 			g.POST("nginx/test", api.TestNginx)
+			// nginx status
+			g.GET("nginx/status", api.NginxStatus)
 
 			g.POST("domain/:name/enable", api.EnableDomain)
 			g.POST("domain/:name/disable", api.DisableDomain)

+ 1 - 0
server/settings/settings.go

@@ -28,6 +28,7 @@ type Server struct {
 	PageSize          int    `json:"page_size"`
 	GithubProxy       string `json:"github_proxy"`
 	NginxConfigDir    string `json:"nginx_config_dir"`
+	NginxPIDPath      string `json:"nginx_pid_path"`
 }
 
 type NginxLog struct {