瀏覽代碼

feat(site): implement site status management and update related components

Jacky 2 周之前
父節點
當前提交
502b656bc5

+ 4 - 9
api/sites/site.go

@@ -28,11 +28,6 @@ func GetSite(c *gin.Context) {
 		return
 	}
 
-	enabled := true
-	if _, err := os.Stat(nginx.GetConfPath("sites-enabled", name)); os.IsNotExist(err) {
-		enabled = false
-	}
-
 	g := query.ChatGPTLog
 	chatgpt, err := g.Where(g.Name.Eq(path)).FirstOrCreate()
 	if err != nil {
@@ -63,15 +58,15 @@ func GetSite(c *gin.Context) {
 			return
 		}
 
-		c.JSON(http.StatusOK, Site{
+		c.JSON(http.StatusOK, site.Site{
 			ModifiedAt:      file.ModTime(),
 			Site:            siteModel,
-			Enabled:         enabled,
 			Name:            name,
 			Config:          string(origContent),
 			AutoCert:        certModel.AutoCert == model.AutoCertEnabled,
 			ChatGPTMessages: chatgpt.Content,
 			Filepath:        path,
+			Status:          site.GetSiteStatus(name),
 		})
 		return
 	}
@@ -96,10 +91,9 @@ func GetSite(c *gin.Context) {
 		}
 	}
 
-	c.JSON(http.StatusOK, Site{
+	c.JSON(http.StatusOK, site.Site{
 		Site:            siteModel,
 		ModifiedAt:      file.ModTime(),
-		Enabled:         enabled,
 		Name:            name,
 		Config:          nginxConfig.FmtCode(),
 		Tokenized:       nginxConfig,
@@ -107,6 +101,7 @@ func GetSite(c *gin.Context) {
 		CertInfo:        certInfoMap,
 		ChatGPTMessages: chatgpt.Content,
 		Filepath:        path,
+		Status:          site.GetSiteStatus(name),
 	})
 }
 

+ 4 - 3
app/src/api/site.ts

@@ -3,15 +3,16 @@ import type { ModelBase } from '@/api/curd'
 import type { EnvGroup } from '@/api/env_group'
 import type { NgxConfig } from '@/api/ngx'
 import type { ChatComplicationMessage } from '@/api/openai'
-import type { PrivateKeyType } from '@/constants'
+import type { ConfigStatus, PrivateKeyType } from '@/constants'
 import Curd from '@/api/curd'
 import http from '@/lib/http'
 
+export type SiteStatus = ConfigStatus.Enabled | ConfigStatus.Disabled | ConfigStatus.Maintenance
+
 export interface Site extends ModelBase {
   modified_at: string
   path: string
   advanced: boolean
-  enabled: boolean
   name: string
   filepath: string
   config: string
@@ -23,7 +24,7 @@ export interface Site extends ModelBase {
   env_group?: EnvGroup
   sync_node_ids: number[]
   urls?: string[]
-  status: string
+  status: SiteStatus
 }
 
 export interface AutoCertRequest {

+ 4 - 2
app/src/views/site/cert/Cert.vue

@@ -1,6 +1,8 @@
 <script setup lang="ts">
 import type { Cert, CertificateInfo } from '@/api/cert'
 import type { NgxDirective } from '@/api/ngx'
+import type { SiteStatus } from '@/api/site'
+import { ConfigStatus } from '@/constants'
 import CertInfo from '@/views/site/cert/CertInfo.vue'
 import ChangeCert from '@/views/site/cert/components/ChangeCert/ChangeCert.vue'
 import IssueCert from '@/views/site/cert/IssueCert.vue'
@@ -9,7 +11,7 @@ const props = defineProps<{
   configName: string
   currentServerIndex: number
   certInfo?: CertificateInfo[]
-  siteEnabled: boolean
+  siteStatus: SiteStatus
 }>()
 
 const current_server_directives = defineModel<NgxDirective[]>('current_server_directives')
@@ -96,7 +98,7 @@ function handleCertChange(certs: Cert[]) {
     <ChangeCert @change="handleCertChange" />
 
     <IssueCert
-      v-if="siteEnabled"
+      v-if="siteStatus === ConfigStatus.Enabled || siteStatus === ConfigStatus.Maintenance"
       v-model:enabled="enabled"
       :config-name="configName"
     />

+ 11 - 36
app/src/views/site/site_edit/components/SiteStatusSegmented.vue → app/src/views/site/components/SiteStatusSegmented.vue

@@ -1,28 +1,17 @@
 <script setup lang="ts">
+import type { SiteStatus } from '@/api/site'
 import site from '@/api/site'
 import { ConfigStatus } from '@/constants'
 import { message, Modal } from 'ant-design-vue'
 
-/**
- * Component props interface
- */
-interface Props {
-  /**
-   * The name of the site configuration
-   */
-  siteName: string
-  /**
-   * Whether the site is enabled
-   */
-  enabled: boolean
-}
-
 // Define props with TypeScript
-const props = defineProps<Props>()
+const props = defineProps<{
+  siteName: string
+}>()
 
 // Define event for status change notification
 const emit = defineEmits<{
-  statusChanged: [{ status: string, enabled: boolean }]
+  statusChanged: [{ status: SiteStatus }]
 }>()
 
 // Use defineModel for v-model binding
@@ -32,73 +21,59 @@ const status = defineModel<string>({
 
 const [modal, ContextHolder] = Modal.useModal()
 
-/**
- * Enable the site
- */
+// Enable the site
 function enable() {
   site.enable(props.siteName).then(() => {
     message.success($gettext('Enabled successfully'))
     status.value = ConfigStatus.Enabled
     emit('statusChanged', {
       status: ConfigStatus.Enabled,
-      enabled: true,
     })
   }).catch(r => {
     message.error($gettext('Failed to enable %{msg}', { msg: r.message ?? '' }), 10)
   })
 }
 
-/**
- * Disable the site
- */
+// Disable the site
 function disable() {
   site.disable(props.siteName).then(() => {
     message.success($gettext('Disabled successfully'))
     status.value = ConfigStatus.Disabled
     emit('statusChanged', {
       status: ConfigStatus.Disabled,
-      enabled: false,
     })
   }).catch(r => {
     message.error($gettext('Failed to disable %{msg}', { msg: r.message ?? '' }))
   })
 }
 
-/**
- * Enable maintenance mode for the site
- */
+// Enable maintenance mode for the site
 function enableMaintenance() {
   site.enableMaintenance(props.siteName).then(() => {
     message.success($gettext('Maintenance mode enabled successfully'))
     status.value = ConfigStatus.Maintenance
     emit('statusChanged', {
       status: ConfigStatus.Maintenance,
-      enabled: true,
     })
   }).catch(r => {
     message.error($gettext('Failed to enable maintenance mode %{msg}', { msg: r.message ?? '' }))
   })
 }
 
-/**
- * Disable maintenance mode for the site
- */
+// Disable maintenance mode for the site
 function disableMaintenance() {
   site.enable(props.siteName).then(() => {
     message.success($gettext('Maintenance mode disabled successfully'))
     status.value = ConfigStatus.Enabled
     emit('statusChanged', {
       status: ConfigStatus.Enabled,
-      enabled: true,
     })
   }).catch(r => {
     message.error($gettext('Failed to disable maintenance mode %{msg}', { msg: r.message ?? '' }))
   })
 }
 
-/**
- * Handle status change from segmented control
- */
+// Handle status change from segmented control
 function onChangeStatus(value: string | number) {
   const statusValue = value as string
   if (statusValue === status.value) {
@@ -148,7 +123,7 @@ function onChangeStatus(value: string | number) {
   <div class="site-status-segmented">
     <ContextHolder />
     <ASegmented
-      v-model:value="status"
+      :value="status"
       :options="[
         {
           value: ConfigStatus.Enabled,

+ 9 - 18
app/src/views/site/ngx_conf/NgxConfigEditor.vue

@@ -1,27 +1,27 @@
 <script setup lang="ts">
 import type { CertificateInfo } from '@/api/cert'
 import type { NgxConfig, NgxDirective } from '@/api/ngx'
+import type { SiteStatus } from '@/api/site'
 import type { CheckedType } from '@/types'
 import type { ComputedRef } from 'vue'
 import template from '@/api/template'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
+import { ConfigStatus } from '@/constants'
 import NginxStatusAlert from '@/views/site/ngx_conf/NginxStatusAlert.vue'
 import NgxServer from '@/views/site/ngx_conf/NgxServer.vue'
 import NgxUpstream from '@/views/site/ngx_conf/NgxUpstream.vue'
 import { Modal } from 'ant-design-vue'
 
-const props = withDefaults(defineProps<{
-  autoCert?: boolean
-  enabled?: boolean
+withDefaults(defineProps<{
+  status?: SiteStatus
   certInfo?: Record<number, CertificateInfo[]>
   context?: 'http' | 'stream'
 }>(), {
-  autoCert: false,
-  enabled: false,
+  status: ConfigStatus.Enabled,
   context: 'http',
 })
 
-const emit = defineEmits(['callback', 'update:autoCert'])
+const autoCert = defineModel<boolean>('autoCert')
 
 const save_config = inject('save_config') as () => Promise<void>
 
@@ -158,15 +158,6 @@ const support_ssl = computed(() => {
   return false
 })
 
-const autoCertRef = computed({
-  get() {
-    return props.autoCert
-  },
-  set(value) {
-    emit('update:autoCert', value)
-  },
-})
-
 provide('directivesMap', directivesMap)
 
 const activeKey = ref(['3'])
@@ -211,9 +202,9 @@ const activeKey = ref(['3'])
         header="Server"
       >
         <NgxServer
-          v-model:auto-cert="autoCertRef"
-          :enabled
-          :cert-info="certInfo"
+          v-model:auto-cert="autoCert"
+          :status
+          :cert-info
           :context
         />
       </ACollapsePanel>

+ 5 - 2
app/src/views/site/ngx_conf/NgxServer.vue

@@ -1,6 +1,8 @@
 <script setup lang="ts">
 import type { CertificateInfo } from '@/api/cert'
 import type { NgxConfig, NgxDirective } from '@/api/ngx'
+import type { SiteStatus } from '@/api/site'
+import { ConfigStatus } from '@/constants'
 import Cert from '@/views/site/cert/Cert.vue'
 import ConfigTemplate from '@/views/site/ngx_conf/config_template/ConfigTemplate.vue'
 import DirectiveEditor from '@/views/site/ngx_conf/directive/DirectiveEditor.vue'
@@ -10,13 +12,14 @@ import { MoreOutlined, PlusOutlined } from '@ant-design/icons-vue'
 import { Modal } from 'ant-design-vue'
 
 withDefaults(defineProps<{
-  enabled: boolean
   certInfo?: {
     [key: number]: CertificateInfo[]
   }
   context?: 'http' | 'stream'
+  status?: SiteStatus
 }>(), {
   context: 'http',
+  status: ConfigStatus.Enabled,
 })
 
 const [modal, ContextHolder] = Modal.useModal()
@@ -125,7 +128,7 @@ provide('ngx_directives', ngx_directives)
             v-model:enabled="autoCert"
             v-model:current_server_directives="ngx_config.servers[current_server_index].directives"
             class="mb-4"
-            :site-enabled="enabled"
+            :site-status="status"
             :config-name="ngx_config.name"
             :cert-info="certInfo?.[k]"
             :current-server-index="current_server_index"

+ 4 - 14
app/src/views/site/site_edit/RightSettings.vue

@@ -1,39 +1,30 @@
 <script setup lang="ts">
 import type { ChatComplicationMessage } from '@/api/openai'
-import type { Site } from '@/api/site'
+import type { Site, SiteStatus } from '@/api/site'
 import type { Ref } from 'vue'
 import envGroup from '@/api/env_group'
 import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
 import NodeSelector from '@/components/NodeSelector/NodeSelector.vue'
 import StdSelector from '@/components/StdDesign/StdDataEntry/components/StdSelector.vue'
-import { ConfigStatus } from '@/constants'
 import { formatDateTime } from '@/lib/helper'
 import { useSettingsStore } from '@/pinia'
 import envGroupColumns from '@/views/environments/group/columns'
+import SiteStatusSegmented from '@/views/site/components/SiteStatusSegmented.vue'
 import ConfigName from '@/views/site/site_edit/components/ConfigName.vue'
-import SiteStatusSegmented from '@/views/site/site_edit/components/SiteStatusSegmented.vue'
 import { InfoCircleOutlined } from '@ant-design/icons-vue'
 
 const settings = useSettingsStore()
 
 const configText = inject('configText') as Ref<string>
-const enabled = inject('enabled') as Ref<boolean>
 const name = inject('name') as ComputedRef<string>
 const filepath = inject('filepath') as Ref<string>
 const historyChatgptRecord = inject('history_chatgpt_record') as Ref<ChatComplicationMessage[]>
 const data = inject('data') as Ref<Site>
 
 const activeKey = ref(['1', '2', '3'])
-const siteStatus = computed(() => {
-  if (!data.value?.status) {
-    return enabled.value ? ConfigStatus.Enabled : ConfigStatus.Disabled
-  }
-  return data.value.status
-})
 
-function handleStatusChanged(event: { status: string, enabled: boolean }) {
+function handleStatusChanged(event: { status: SiteStatus }) {
   data.value.status = event.status
-  enabled.value = event.enabled
 }
 </script>
 
@@ -54,9 +45,8 @@ function handleStatusChanged(event: { status: string, enabled: boolean }) {
         <AForm layout="vertical">
           <AFormItem :label="$gettext('Status')">
             <SiteStatusSegmented
-              v-model="siteStatus"
+              v-model="data.status"
               :site-name="name"
-              :enabled="enabled"
               @status-changed="handleStatusChanged"
             />
           </AFormItem>

+ 11 - 8
app/src/views/site/site_edit/SiteEdit.vue

@@ -2,7 +2,6 @@
 import type { CertificateInfo } from '@/api/cert'
 import type { NgxConfig } from '@/api/ngx'
 import type { ChatComplicationMessage } from '@/api/openai'
-
 import type { Site } from '@/api/site'
 import type { CheckedType } from '@/types'
 import config from '@/api/config'
@@ -11,6 +10,7 @@ import site from '@/api/site'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 import { ConfigHistory } from '@/components/ConfigHistory'
 import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
+import { ConfigStatus } from '@/constants'
 import NgxConfigEditor from '@/views/site/ngx_conf/NgxConfigEditor.vue'
 import RightSettings from '@/views/site/site_edit/RightSettings.vue'
 import { HistoryOutlined } from '@ant-design/icons-vue'
@@ -30,7 +30,6 @@ const ngx_config: NgxConfig = reactive({
 const certInfoMap: Ref<Record<number, CertificateInfo[]>> = ref({})
 
 const autoCert = ref(false)
-const enabled = ref(false)
 const filepath = ref('')
 const configText = ref('')
 const advanceModeRef = ref(false)
@@ -64,7 +63,6 @@ async function handleResponse(r: Site) {
   filename.value = r.name
   filepath.value = r.filepath
   configText.value = r.config
-  enabled.value = r.enabled
   autoCert.value = r.auto_cert
   historyChatgptRecord.value = r.chatgpt_messages
   data.value = r
@@ -168,7 +166,6 @@ provide('save_config', save)
 provide('configText', configText)
 provide('ngx_config', ngx_config)
 provide('history_chatgpt_record', historyChatgptRecord)
-provide('enabled', enabled)
 provide('name', name)
 provide('filepath', filepath)
 provide('data', data)
@@ -187,17 +184,23 @@ provide('data', data)
         <template #title>
           <span style="margin-right: 10px">{{ $gettext('Edit %{n}', { n: name }) }}</span>
           <ATag
-            v-if="enabled"
+            v-if="data.status === ConfigStatus.Enabled"
             color="blue"
           >
             {{ $gettext('Enabled') }}
           </ATag>
           <ATag
-            v-else
-            color="orange"
+            v-else-if="data.status === ConfigStatus.Disabled"
+            color="red"
           >
             {{ $gettext('Disabled') }}
           </ATag>
+          <ATag
+            v-else-if="data.status === ConfigStatus.Maintenance"
+            color="orange"
+          >
+            {{ $gettext('Maintenance') }}
+          </ATag>
         </template>
         <template #extra>
           <ASpace>
@@ -260,7 +263,7 @@ provide('data', data)
             <NgxConfigEditor
               v-model:auto-cert="autoCert"
               :cert-info="certInfoMap"
-              :enabled="enabled"
+              :status="data.status"
               @callback="save"
             />
           </div>

+ 3 - 3
app/src/views/site/site_list/columns.tsx

@@ -1,3 +1,4 @@
+import type { SiteStatus } from '@/api/site'
 import type {
   CustomRender,
 } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
@@ -10,7 +11,7 @@ import {
 import { input, select, selector } from '@/components/StdDesign/StdDataEntry'
 import { ConfigStatus } from '@/constants'
 import envGroupColumns from '@/views/environments/group/columns'
-import SiteStatusSegmented from '@/views/site/site_edit/components/SiteStatusSegmented.vue'
+import SiteStatusSegmented from '@/views/site/components/SiteStatusSegmented.vue'
 import { Tag } from 'ant-design-vue'
 
 const columns: Column[] = [{
@@ -98,9 +99,8 @@ const columns: Column[] = [{
         // This will be handled by the component internal events
         record.status = val
       },
-      'onStatusChanged': ({ status, enabled }: { status: string, enabled: boolean }) => {
+      'onStatusChanged': ({ status }: { status: SiteStatus }) => {
         record.status = status
-        record.enabled = enabled
       },
     })
   },

+ 5 - 1
internal/performance/perf_opt.go

@@ -10,6 +10,7 @@ import (
 	"github.com/tufanbarisyildirim/gonginx/config"
 	"github.com/tufanbarisyildirim/gonginx/dumper"
 	"github.com/tufanbarisyildirim/gonginx/parser"
+	"github.com/uozi-tech/cosy/logger"
 )
 
 type ProxyCacheConfig struct {
@@ -189,7 +190,10 @@ func updateOrRemoveProxyCachePath(block config.IBlock, directives []config.IDire
 	// First parameter is the path (required)
 	if proxyCache.Path != "" {
 		params = append(params, config.Parameter{Value: proxyCache.Path})
-		_ = os.MkdirAll(proxyCache.Path, 0755)
+		err := os.MkdirAll(proxyCache.Path, 0755)
+		if err != nil {
+			logger.Error("failed to create proxy cache path", err)
+		}
 	} else {
 		// No path specified, can't add the directive
 		return

+ 21 - 0
internal/site/status.go

@@ -0,0 +1,21 @@
+package site
+
+import (
+	"github.com/0xJacky/Nginx-UI/internal/helper"
+	"github.com/0xJacky/Nginx-UI/internal/nginx"
+)
+
+// GetSiteStatus returns the status of the site
+func GetSiteStatus(name string) SiteStatus {
+	enabledFilePath := nginx.GetConfPath("sites-enabled", name)
+	if helper.FileExists(enabledFilePath) {
+		return SiteStatusEnabled
+	}
+
+	mantainanceFilePath := nginx.GetConfPath("sites-enabled", name+MaintenanceSuffix)
+	if helper.FileExists(mantainanceFilePath) {
+		return SiteStatusMaintenance
+	}
+
+	return SiteStatusDisabled
+}

+ 12 - 3
api/sites/type.go → internal/site/type.go

@@ -1,18 +1,27 @@
-package sites
+package site
 
 import (
+	"time"
+
 	"github.com/0xJacky/Nginx-UI/internal/cert"
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/sashabaranov/go-openai"
-	"time"
+)
+
+type SiteStatus string
+
+const (
+	SiteStatusEnabled     SiteStatus = "enabled"
+	SiteStatusDisabled    SiteStatus = "disabled"
+	SiteStatusMaintenance SiteStatus = "maintenance"
 )
 
 type Site struct {
 	*model.Site
 	Name            string                         `json:"name"`
 	ModifiedAt      time.Time                      `json:"modified_at"`
-	Enabled         bool                           `json:"enabled"`
+	Status          SiteStatus                     `json:"status"`
 	Config          string                         `json:"config"`
 	AutoCert        bool                           `json:"auto_cert"`
 	ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`