mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
feat(site): implement site status management and update related components
This commit is contained in:
parent
402de5d987
commit
502b656bc5
12 changed files with 93 additions and 99 deletions
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
|
|
@ -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,
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
},
|
||||
})
|
||||
},
|
||||
|
|
|
@ -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
21
internal/site/status.go
Normal 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
|
||||
}
|
|
@ -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"`
|
Loading…
Add table
Add a link
Reference in a new issue