feat: display info of multiple certificates in server tab.

This commit is contained in:
Jacky 2024-07-24 16:33:45 +08:00
parent ada02323d8
commit e1a5521f4a
No known key found for this signature in database
GPG key ID: 215C21B10DF38B4D
9 changed files with 54 additions and 61 deletions

View file

@ -66,7 +66,6 @@ func GetDomains(c *gin.Context) {
func GetDomain(c *gin.Context) { func GetDomain(c *gin.Context) {
rewriteName, ok := c.Get("rewriteConfigFileName") rewriteName, ok := c.Get("rewriteConfigFileName")
name := c.Param("name") name := c.Param("name")
// for modify filename // for modify filename
@ -84,14 +83,12 @@ func GetDomain(c *gin.Context) {
} }
enabled := true enabled := true
if _, err := os.Stat(nginx.GetConfPath("sites-enabled", name)); os.IsNotExist(err) { if _, err := os.Stat(nginx.GetConfPath("sites-enabled", name)); os.IsNotExist(err) {
enabled = false enabled = false
} }
g := query.ChatGPTLog g := query.ChatGPTLog
chatgpt, err := g.Where(g.Name.Eq(path)).FirstOrCreate() chatgpt, err := g.Where(g.Name.Eq(path)).FirstOrCreate()
if err != nil { if err != nil {
api.ErrHandler(c, err) api.ErrHandler(c, err)
return return
@ -103,14 +100,12 @@ func GetDomain(c *gin.Context) {
s := query.Site s := query.Site
site, err := s.Where(s.Path.Eq(path)).FirstOrInit() site, err := s.Where(s.Path.Eq(path)).FirstOrInit()
if err != nil { if err != nil {
api.ErrHandler(c, err) api.ErrHandler(c, err)
return return
} }
certModel, err := model.FirstCert(name) certModel, err := model.FirstCert(name)
if err != nil { if err != nil {
logger.Warn(err) logger.Warn(err)
} }
@ -136,28 +131,21 @@ func GetDomain(c *gin.Context) {
} }
nginxConfig, err := nginx.ParseNgxConfig(path) nginxConfig, err := nginx.ParseNgxConfig(path)
if err != nil { if err != nil {
api.ErrHandler(c, err) api.ErrHandler(c, err)
return return
} }
certInfoMap := make(map[int]*cert.Info) certInfoMap := make(map[int][]*cert.Info)
for serverIdx, server := range nginxConfig.Servers { for serverIdx, server := range nginxConfig.Servers {
for _, directive := range server.Directives { for _, directive := range server.Directives {
if directive.Directive == "ssl_certificate" { if directive.Directive == "ssl_certificate" {
pubKey, err := cert.GetCertInfo(directive.Params) pubKey, err := cert.GetCertInfo(directive.Params)
if err != nil { if err != nil {
logger.Error("Failed to get certificate information", err) logger.Error("Failed to get certificate information", err)
break continue
} }
certInfoMap[serverIdx] = append(certInfoMap[serverIdx], pubKey)
certInfoMap[serverIdx] = pubKey
break
} }
} }
} }
@ -291,9 +279,8 @@ func EnableDomain(c *gin.Context) {
} }
} }
// Test nginx config, if not pass then disable the site. // Test nginx config, if not pass, then disable the site.
output := nginx.TestConf() output := nginx.TestConf()
if nginx.GetLogLevel(output) > nginx.Warn { if nginx.GetLogLevel(output) > nginx.Warn {
_ = os.Remove(enabledConfigFilePath) _ = os.Remove(enabledConfigFilePath)
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
@ -318,16 +305,13 @@ func EnableDomain(c *gin.Context) {
func DisableDomain(c *gin.Context) { func DisableDomain(c *gin.Context) {
enabledConfigFilePath := nginx.GetConfPath("sites-enabled", c.Param("name")) enabledConfigFilePath := nginx.GetConfPath("sites-enabled", c.Param("name"))
_, err := os.Stat(enabledConfigFilePath) _, err := os.Stat(enabledConfigFilePath)
if err != nil { if err != nil {
api.ErrHandler(c, err) api.ErrHandler(c, err)
return return
} }
err = os.Remove(enabledConfigFilePath) err = os.Remove(enabledConfigFilePath)
if err != nil { if err != nil {
api.ErrHandler(c, err) api.ErrHandler(c, err)
return return
@ -342,7 +326,6 @@ func DisableDomain(c *gin.Context) {
} }
output := nginx.Reload() output := nginx.Reload()
if nginx.GetLogLevel(output) > nginx.Warn { if nginx.GetLogLevel(output) > nginx.Warn {
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"message": output, "message": output,
@ -360,7 +343,6 @@ func DeleteDomain(c *gin.Context) {
name := c.Param("name") name := c.Param("name")
availablePath := nginx.GetConfPath("sites-available", name) availablePath := nginx.GetConfPath("sites-available", name)
enabledPath := nginx.GetConfPath("sites-enabled", name) enabledPath := nginx.GetConfPath("sites-enabled", name)
if _, err = os.Stat(availablePath); os.IsNotExist(err) { if _, err = os.Stat(availablePath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{ c.JSON(http.StatusNotFound, gin.H{
"message": "site not found", "message": "site not found",
@ -379,7 +361,6 @@ func DeleteDomain(c *gin.Context) {
_ = certModel.Remove() _ = certModel.Remove()
err = os.Remove(availablePath) err = os.Remove(availablePath)
if err != nil { if err != nil {
api.ErrHandler(c, err) api.ErrHandler(c, err)
return return

View file

@ -16,6 +16,6 @@ type Site struct {
AutoCert bool `json:"auto_cert"` AutoCert bool `json:"auto_cert"`
ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"` ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`
Tokenized *nginx.NgxConfig `json:"tokenized,omitempty"` Tokenized *nginx.NgxConfig `json:"tokenized,omitempty"`
CertInfo map[int]*cert.Info `json:"cert_info,omitempty"` CertInfo map[int][]*cert.Info `json:"cert_info,omitempty"`
Filepath string `json:"filepath"` Filepath string `json:"filepath"`
} }

View file

@ -15,9 +15,7 @@ export interface Site {
auto_cert: boolean auto_cert: boolean
chatgpt_messages: ChatComplicationMessage[] chatgpt_messages: ChatComplicationMessage[]
tokenized?: NgxConfig tokenized?: NgxConfig
cert_info?: { cert_info?: Record<number, CertificateInfo[]>
[key: number]: CertificateInfo
}
} }
export interface AutoCertRequest { export interface AutoCertRequest {

View file

@ -126,7 +126,10 @@ const isManaged = computed(() => {
layout="vertical" layout="vertical"
> >
<AFormItem :label="$gettext('Certificate Status')"> <AFormItem :label="$gettext('Certificate Status')">
<CertInfo :cert="data.certificate_info" /> <CertInfo
:cert="data.certificate_info"
class="max-w-96"
/>
</AFormItem> </AFormItem>
</AForm> </AForm>

View file

@ -29,7 +29,7 @@ const ngx_config: NgxConfig = reactive({
servers: [], servers: [],
}) })
const cert_info_map: Record<string, CertificateInfo> = reactive({}) const certInfoMap: Ref<Record<number, CertificateInfo[]>> = ref({})
const auto_cert = ref(false) const auto_cert = ref(false)
const enabled = ref(false) const enabled = ref(false)
@ -62,9 +62,6 @@ function handle_response(r: Site) {
if (r.advanced) if (r.advanced)
advance_mode.value = true advance_mode.value = true
Object.keys(cert_info_map).forEach((v: string) => {
delete cert_info_map[v]
})
parse_error_status.value = false parse_error_status.value = false
parse_error_message.value = '' parse_error_message.value = ''
filename.value = r.name filename.value = r.name
@ -74,8 +71,8 @@ function handle_response(r: Site) {
auto_cert.value = r.auto_cert auto_cert.value = r.auto_cert
history_chatgpt_record.value = r.chatgpt_messages history_chatgpt_record.value = r.chatgpt_messages
data.value = r data.value = r
certInfoMap.value = r.cert_info || {}
Object.assign(ngx_config, r.tokenized) Object.assign(ngx_config, r.tokenized)
Object.assign(cert_info_map, r.cert_info)
} }
function init() { function init() {
@ -230,7 +227,7 @@ provide('data', data)
> >
<NgxConfigEditor <NgxConfigEditor
v-model:auto-cert="auto_cert" v-model:auto-cert="auto_cert"
:cert-info="cert_info_map" :cert-info="certInfoMap"
:enabled="enabled" :enabled="enabled"
@callback="save" @callback="save"
/> />

View file

@ -8,7 +8,7 @@ const props = defineProps<{
configName: string configName: string
enabled: boolean enabled: boolean
currentServerIndex: number currentServerIndex: number
certInfo?: CertificateInfo certInfo?: CertificateInfo[]
}>() }>()
const emit = defineEmits(['update:enabled']) const emit = defineEmits(['update:enabled'])
@ -25,13 +25,23 @@ const enabled = computed({
<template> <template>
<div> <div>
<h2> <h3>
{{ $gettext('Certificate Status') }} {{ $gettext('Certificate Status') }}
</h2> </h3>
<CertInfo
:cert="certInfo" <ARow
:gutter="[16, 16]"
class="mb-4" class="mb-4"
/> >
<ACol
v-for="(c, index) in certInfo"
:key="index"
:xs="24"
:sm="12"
>
<CertInfo :cert="c" />
</ACol>
</ARow>
<ChangeCert /> <ChangeCert />

View file

@ -1,42 +1,46 @@
<script setup lang="ts"> <script setup lang="ts">
import { CheckCircleOutlined, CloseCircleOutlined } from '@ant-design/icons-vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { CertificateInfo } from '@/api/cert' import type { CertificateInfo } from '@/api/cert'
defineProps<{ const props = defineProps<{
cert?: CertificateInfo cert?: CertificateInfo
}>() }>()
const isValid = computed(() => dayjs().isAfter(props.cert?.not_before) && dayjs().isBefore(props.cert?.not_after))
</script> </script>
<template> <template>
<div <ACard
v-if="cert" v-if="cert"
class="cert-info" size="small"
> >
<template #title>
{{ cert.subject_name }}
<ATag
v-if="isValid"
color="success"
class="ml-2"
>
{{ $gettext('Valid') }}
</ATag>
<ATag
v-else
color="error"
class="ml-2"
>
{{ $gettext('Expired') }}
</ATag>
</template>
<p> <p>
{{ $gettext('Intermediate Certification Authorities: %{issuer}', { issuer: cert.issuer_name }) }} {{ $gettext('Intermediate Certification Authorities: %{issuer}', { issuer: cert.issuer_name }) }}
</p> </p>
<p>
{{ $gettext('Subject Name: %{subject}', { subject: cert.subject_name }) }}
</p>
<p> <p>
{{ $gettext('Expired At: %{date}', { date: dayjs(cert.not_after).format('YYYY-MM-DD HH:mm:ss').toString() }) }} {{ $gettext('Expired At: %{date}', { date: dayjs(cert.not_after).format('YYYY-MM-DD HH:mm:ss').toString() }) }}
</p> </p>
<p> <p class="mb-0">
{{ $gettext('Not Valid Before: %{date}', { date: dayjs(cert.not_before).format('YYYY-MM-DD HH:mm:ss').toString() }) }} {{ $gettext('Not Valid Before: %{date}', { date: dayjs(cert.not_before).format('YYYY-MM-DD HH:mm:ss').toString() }) }}
</p> </p>
<div class="status"> </ACard>
<template v-if="dayjs().isBefore(cert.not_before) || dayjs().isAfter(cert.not_after)">
<CloseCircleOutlined class="text-red-600" />
<span class="ml-2">{{ $gettext('Certificate has expired') }}</span>
</template>
<template v-else>
<CheckCircleOutlined class="text-green-500" />
<span class="ml-2">{{ $gettext('Certificate is valid') }}</span>
</template>
</div>
</div>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>

View file

@ -12,7 +12,7 @@ import type { CheckedType } from '@/types'
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
autoCert?: boolean autoCert?: boolean
enabled: boolean enabled: boolean
certInfo?: Record<number, CertificateInfo> certInfo?: Record<number, CertificateInfo[]>
context?: 'http' | 'stream' context?: 'http' | 'stream'
}>(), { }>(), {
autoCert: false, autoCert: false,

View file

@ -12,7 +12,7 @@ import type { CertificateInfo } from '@/api/cert'
withDefaults(defineProps<{ withDefaults(defineProps<{
enabled: boolean enabled: boolean
certInfo?: { certInfo?: {
[key: number]: CertificateInfo [key: number]: CertificateInfo[]
} }
context: 'http' | 'stream' context: 'http' | 'stream'
}>(), { }>(), {