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

This commit is contained in:
Jacky 2025-04-14 02:11:53 +00:00
parent 402de5d987
commit 502b656bc5
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
12 changed files with 93 additions and 99 deletions

View file

@ -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),
})
}

View file

@ -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 {

View file

@ -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"
/>

View file

@ -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,

View file

@ -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>

View file

@ -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"

View file

@ -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>

View file

@ -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>

View file

@ -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
},
})
},

View file

@ -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
internal/site/status.go Normal file
View file

@ -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
}

View file

@ -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"`