Browse Source

feat: add notifications for renew certs #192

0xJacky 1 year ago
parent
commit
994b9087ae
45 changed files with 1971 additions and 594 deletions
  1. 12 0
      .idea/dataSources.xml
  2. 6 0
      api/cosy/cosy.go
  3. 4 0
      api/cosy/delete.go
  4. 58 0
      api/notification/notification.go
  5. 10 0
      api/notification/router.go
  6. 3 0
      app/components.d.ts
  7. 3 0
      app/gettext.config.cjs
  8. 19 0
      app/src/api/notification.ts
  9. 164 0
      app/src/components/Notification/Notification.vue
  10. 9 5
      app/src/components/StdDesign/StdDataDisplay/StdCurd.vue
  11. 15 9
      app/src/components/StdDesign/StdDataDisplay/StdTable.vue
  12. 17 0
      app/src/constants/index.ts
  13. 3 0
      app/src/language/constants.ts
  14. 143 69
      app/src/language/en/app.po
  15. 143 68
      app/src/language/es/app.po
  16. 143 68
      app/src/language/fr_FR/app.po
  17. 138 65
      app/src/language/messages.pot
  18. 143 69
      app/src/language/ru_RU/app.po
  19. BIN
      app/src/language/zh_CN/app.mo
  20. 133 68
      app/src/language/zh_CN/app.po
  21. 143 68
      app/src/language/zh_TW/app.po
  22. 3 0
      app/src/layouts/HeaderLayout.vue
  23. 1 0
      app/src/pinia/moudule/user.ts
  24. 9 0
      app/src/routes/index.ts
  25. 12 4
      app/src/views/certificate/Certificate.vue
  26. 40 12
      app/src/views/certificate/CertificateEditor.vue
  27. 0 1
      app/src/views/domain/DomainList.vue
  28. 87 0
      app/src/views/notification/Notification.vue
  29. 1 1
      cmd/generate/generate.go
  30. 28 12
      internal/cert/auto_cert.go
  31. 32 0
      internal/notification/notification.go
  32. 3 6
      model/model.go
  33. 17 0
      model/notification.go
  34. 2 5
      query/auth_tokens.gen.go
  35. 2 5
      query/auths.gen.go
  36. 31 34
      query/certs.gen.go
  37. 2 5
      query/chat_gpt_logs.gen.go
  38. 2 5
      query/config_backups.gen.go
  39. 2 5
      query/dns_credentials.gen.go
  40. 2 5
      query/environments.gen.go
  41. 8 0
      query/gen.go
  42. 374 0
      query/notifications.gen.go
  43. 2 5
      query/sites.gen.go
  44. BIN
      resources/demo/demo.db
  45. 2 0
      router/routers.go

+ 12 - 0
.idea/dataSources.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
+    <data-source source="LOCAL" name="database" uuid="31ab1005-9ff6-47e2-9df8-6c521ae42819">
+      <driver-ref>sqlite.xerial</driver-ref>
+      <synchronize>true</synchronize>
+      <jdbc-driver>org.sqlite.JDBC</jdbc-driver>
+      <jdbc-url>jdbc:sqlite:$PROJECT_DIR$/database.db</jdbc-url>
+      <working-dir>$ProjectFileDir$</working-dir>
+    </data-source>
+  </component>
+</project>

+ 6 - 0
api/cosy/cosy.go

@@ -27,6 +27,7 @@ type Ctx[T any] struct {
 	preloads              []string
 	scan                  func(tx *gorm.DB) any
 	transformer           func(*T) any
+	permanentlyDelete     bool
 	SelectedFields        []string
 }
 
@@ -128,6 +129,11 @@ func (c *Ctx[T]) Abort() {
 	c.abort = true
 }
 
+func (c *Ctx[T]) PermanentlyDelete() *Ctx[T] {
+	c.permanentlyDelete = true
+	return c
+}
+
 func (c *Ctx[T]) GormScope(hook func(tx *gorm.DB) *gorm.DB) *Ctx[T] {
 	c.gormScopes = append(c.gormScopes, hook)
 	return c

+ 4 - 0
api/cosy/delete.go

@@ -29,6 +29,10 @@ func (c *Ctx[T]) Destroy() {
 		return
 	}
 
+	if c.permanentlyDelete {
+		result = result.Unscoped()
+	}
+
 	err = result.Delete(&dbModel).Error
 	if err != nil {
 		errHandler(c.ctx, err)

+ 58 - 0
api/notification/notification.go

@@ -0,0 +1,58 @@
+package notification
+
+import (
+	"github.com/0xJacky/Nginx-UI/api"
+	"github.com/0xJacky/Nginx-UI/api/cosy"
+	"github.com/0xJacky/Nginx-UI/model"
+	"github.com/0xJacky/Nginx-UI/query"
+	"github.com/gin-gonic/gin"
+	"github.com/spf13/cast"
+	"net/http"
+)
+
+func Get(c *gin.Context) {
+	n := query.Notification
+
+	id := cast.ToInt(c.Param("id"))
+
+	data, err := n.FirstByID(id)
+
+	if err != nil {
+		api.ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, data)
+}
+
+func GetList(c *gin.Context) {
+	cosy.Core[model.Notification](c).PagingList()
+}
+
+func Destroy(c *gin.Context) {
+	cosy.Core[model.Notification](c).
+		PermanentlyDelete().
+		Destroy()
+}
+
+func DestroyAll(c *gin.Context) {
+	db := model.UseDB()
+	// remove all records
+	err := db.Exec("DELETE FROM notifications").Error
+
+	if err != nil {
+		api.ErrHandler(c, err)
+		return
+	}
+	// reset auto increment
+	err = db.Exec("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'notifications';").Error
+
+	if err != nil {
+		api.ErrHandler(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "ok",
+	})
+}

+ 10 - 0
api/notification/router.go

@@ -0,0 +1,10 @@
+package notification
+
+import "github.com/gin-gonic/gin"
+
+func InitRouter(r *gin.RouterGroup) {
+	r.GET("notifications", GetList)
+	r.GET("notification/:id", Get)
+	r.DELETE("notification/:id", Destroy)
+	r.DELETE("notifications", DestroyAll)
+}

+ 3 - 0
app/components.d.ts

@@ -40,6 +40,7 @@ declare module 'vue' {
     AListItem: typeof import('ant-design-vue/es')['ListItem']
     AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
     AMenu: typeof import('ant-design-vue/es')['Menu']
+    AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
     AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
     AModal: typeof import('ant-design-vue/es')['Modal']
     APagination: typeof import('ant-design-vue/es')['Pagination']
@@ -73,6 +74,8 @@ declare module 'vue' {
     LogoLogo: typeof import('./src/components/Logo/Logo.vue')['default']
     NginxControlNginxControl: typeof import('./src/components/NginxControl/NginxControl.vue')['default']
     NodeSelectorNodeSelector: typeof import('./src/components/NodeSelector/NodeSelector.vue')['default']
+    NotificationClearNotifications: typeof import('./src/components/Notification/ClearNotifications.vue')['default']
+    NotificationNotification: typeof import('./src/components/Notification/Notification.vue')['default']
     PageHeaderPageHeader: typeof import('./src/components/PageHeader/PageHeader.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 3 - 0
app/gettext.config.cjs

@@ -2,6 +2,9 @@
 const i18n = require('./i18n.json')
 
 module.exports = {
+  input: {
+    include: ["**/*.js", "**/*.ts", "**/*.vue", "**/*.jsx", "**/*.tsx"]
+  },
   output: {
     locales: Object.keys(i18n),
   },

+ 19 - 0
app/src/api/notification.ts

@@ -0,0 +1,19 @@
+import type { ModelBase } from '@/api/curd'
+import Curd from '@/api/curd'
+import http from '@/lib/http'
+
+export interface Notification extends ModelBase {
+  type: string
+  title: string
+  details: string
+}
+
+class NotificationCurd extends Curd<Notification> {
+  public clear() {
+    return http.delete(this.plural)
+  }
+}
+
+const notification = new NotificationCurd('/notification')
+
+export default notification

+ 164 - 0
app/src/components/Notification/Notification.vue

@@ -0,0 +1,164 @@
+<script setup lang="ts">
+import { BellOutlined, CheckCircleOutlined, CloseCircleOutlined, DeleteOutlined, InfoCircleOutlined, WarningOutlined } from '@ant-design/icons-vue'
+import { useGettext } from 'vue3-gettext'
+import type { Ref } from 'vue'
+import { message } from 'ant-design-vue'
+import notification from '@/api/notification'
+import type { Notification } from '@/api/notification'
+import { NotificationTypeT } from '@/constants'
+import { useUserStore } from '@/pinia'
+
+const { $gettext } = useGettext()
+const loading = ref(false)
+
+const { unreadCount } = storeToRefs(useUserStore())
+
+const data = ref([]) as Ref<Notification[]>
+function init() {
+  loading.value = true
+  notification.get_list().then(r => {
+    data.value = r.data
+    unreadCount.value = r.pagination.total
+  }).catch(e => {
+    message.error($gettext(e?.message ?? 'Server error'))
+  }).finally(() => {
+    loading.value = false
+  })
+}
+
+onMounted(() => {
+  init()
+})
+
+const open = ref(false)
+
+watch(open, v => {
+  if (v)
+    init()
+})
+
+function clear() {
+  notification.clear().then(() => {
+    message.success($gettext('Cleared successfully'))
+    data.value = []
+    unreadCount.value = 0
+  }).catch(e => {
+    message.error($gettext(e?.message ?? 'Server error'))
+  })
+}
+
+function remove(id: number) {
+  notification.destroy(id).then(() => {
+    message.success($gettext('Removed successfully'))
+    init()
+  }).catch(e => {
+    message.error($gettext(e?.message ?? 'Server error'))
+  })
+}
+
+const router = useRouter()
+function viewAll() {
+  router.push('/notifications')
+  open.value = false
+}
+</script>
+
+<template>
+  <span class="cursor-pointer">
+    <APopover
+      v-model:open="open"
+      placement="bottomRight"
+      overlay-class-name="notification-popover"
+      trigger="click"
+    >
+      <ABadge
+        :count="unreadCount"
+        dot
+      >
+        <BellOutlined />
+      </ABadge>
+      <template #content>
+        <div class="flex justify-between items-center p-2">
+          <h3 class="mb-0">{{ $gettext('Notifications') }}</h3>
+          <APopconfirm
+            :cancel-text="$gettext('No')"
+            :ok-text="$gettext('OK')"
+            :title="$gettext('Are you sure you want to clear all notifications?')"
+            placement="bottomRight"
+            @confirm="clear"
+          >
+            <a>
+              {{ $gettext('Clear') }}
+            </a>
+          </APopconfirm>
+        </div>
+
+        <ADivider class="mt-2 mb-2" />
+
+        <AList
+          :data-source="data"
+          class="max-h-96 overflow-scroll"
+        >
+          <template #renderItem="{ item }">
+            <AListItem>
+              <template #actions>
+                <span
+                  key="list-loadmore-remove"
+                  class="cursor-pointer"
+                  @click="remove(item.id)"
+                >
+                  <DeleteOutlined />
+                </span>
+              </template>
+              <AListItemMeta
+                :title="item.title"
+                :description="item.details"
+              >
+                <template #avatar>
+                  <div>
+                    <CloseCircleOutlined
+                      v-if="item.type === NotificationTypeT.Error"
+                      class="text-red-500"
+                    />
+                    <WarningOutlined
+                      v-else-if="item.type === NotificationTypeT.Warning"
+                      class="text-orange-400"
+                    />
+                    <InfoCircleOutlined
+                      v-else-if="item.type === NotificationTypeT.Info"
+                      class="text-blue-500"
+                    />
+                    <CheckCircleOutlined
+                      v-else-if="item.type === NotificationTypeT.Success"
+                      class="text-green-500"
+                    />
+                  </div>
+                </template>
+              </AListItemMeta>
+            </AListItem>
+          </template>
+        </AList>
+        <ADivider class="m-0 mb-2" />
+        <div class="flex justify-center p-2">
+          <a @click="viewAll">{{ $gettext('View all notifications') }}</a>
+        </div>
+      </template>
+    </APopover>
+  </span>
+</template>
+
+<style lang="less">
+.notification-popover {
+  width: 400px;
+}
+</style>
+
+<style scoped lang="less">
+:deep(.ant-list-item-meta) {
+  align-items: center !important;
+}
+
+:deep(.ant-list-item-meta-avatar) {
+  font-size: 24px;
+}
+</style>

+ 9 - 5
app/src/components/StdDesign/StdDataDisplay/StdCurd.vue

@@ -107,11 +107,15 @@ const selectedRowKeys = ref([])
 <template>
   <div class="std-curd">
     <ACard :title="title || $gettext('Table')">
-      <template
-        v-if="!disableAdd"
-        #extra
-      >
-        <a @click="add">{{ $gettext('Add') }}</a>
+      <template #extra>
+        <ASpace>
+          <a
+            v-if="!disableAdd"
+            @click="add"
+          >{{ $gettext('Add') }}</a>
+
+          <slot name="extra" />
+        </ASpace>
       </template>
 
       <StdTable

+ 15 - 9
app/src/components/StdDesign/StdDataDisplay/StdTable.vue

@@ -327,20 +327,26 @@ function export_csv() {
           {{ text }}
         </template>
         <template v-if="column.dataIndex === 'action'">
-          <AButton
-            v-if="!props.disabledModify"
-            type="link"
-            size="small"
-            @click="$emit('clickEdit', record[props.rowKey], record)"
-          >
-            {{ $gettext('Modify') }}
-          </AButton>
+          <template v-if="!props.disabledModify">
+            <AButton
+              type="link"
+              size="small"
+              @click="$emit('clickEdit', record[props.rowKey], record)"
+            >
+              {{ $gettext('Modify') }}
+            </AButton>
+            <ADivider
+              v-if="!props.disableDelete"
+              type="vertical"
+            />
+          </template>
+
           <slot
             name="actions"
             :record="record"
           />
+
           <template v-if="!props.disableDelete">
-            <ADivider type="vertical" />
             <APopconfirm
               :cancel-text="$gettext('No')"
               :ok-text="$gettext('OK')"

+ 17 - 0
app/src/constants/index.ts

@@ -1,4 +1,21 @@
+import gettext from '@/gettext'
+
+const { $gettext } = gettext
 export enum AutoCertState {
   Disable = 0,
   Enable = 1,
 }
+
+export enum NotificationTypeT {
+  Error,
+  Warning,
+  Info,
+  Success,
+}
+
+export const NotificationType = {
+  [NotificationTypeT.Error]: () => $gettext('Error'),
+  [NotificationTypeT.Warning]: () => $gettext('Warning'),
+  [NotificationTypeT.Info]: () => $gettext('Info'),
+  [NotificationTypeT.Success]: () => $gettext('Success'),
+} as const

+ 3 - 0
app/src/language/constants.ts

@@ -33,4 +33,7 @@ export const msg = [
 
   $gettext('File exists'),
   $gettext('Requested with wrong parameters'),
+
+  $gettext('Renew Certificate Success'),
+  $gettext('Renew Certificate Error'),
 ]

+ 143 - 69
app/src/language/en/app.po

@@ -9,30 +9,30 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: src/routes/index.ts:199
+#: src/routes/index.ts:208
 msgid "About"
 msgstr "About"
 
-#: src/routes/index.ts:151 src/views/domain/ngx_conf/LogEntry.vue:78
+#: src/routes/index.ts:152 src/views/domain/ngx_conf/LogEntry.vue:78
 msgid "Access Logs"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:96
+#: src/views/certificate/Certificate.vue:104
 #: src/views/certificate/DNSCredential.vue:32 src/views/config/config.ts:36
 #: src/views/domain/DomainList.vue:50 src/views/environment/Environment.vue:105
-#: src/views/user/User.vue:46
+#: src/views/notification/Notification.vue:38 src/views/user/User.vue:46
 msgid "Action"
 msgstr "Action"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:114
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:115
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
+#: src/views/certificate/Certificate.vue:120
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:115
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:308
 msgid "Add"
 msgstr ""
 
-#: src/routes/index.ts:122 src/views/certificate/Certificate.vue:115
-#: src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:123 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Add Certificate"
 msgstr "Certificate Status"
@@ -46,7 +46,7 @@ msgstr "Add Directive Below"
 msgid "Add Location"
 msgstr "Add Location"
 
-#: src/routes/index.ts:70 src/views/domain/DomainAdd.vue:91
+#: src/routes/index.ts:71 src/views/domain/DomainAdd.vue:91
 msgid "Add Site"
 msgstr "Add Site"
 
@@ -75,13 +75,19 @@ msgstr ""
 msgid "Arch"
 msgstr ""
 
+#: src/components/Notification/Notification.vue:86
+#: src/views/notification/Notification.vue:73
+#, fuzzy
+msgid "Are you sure you want to clear all notifications?"
+msgstr "Are you sure you want to remove this directive?"
+
 #: src/components/ChatGPT/ChatGPT.vue:279
 #, fuzzy
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Are you sure you want to remove this directive?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:347
-#: src/views/domain/DomainList.vue:145
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:353
+#: src/views/domain/DomainList.vue:144
 #, fuzzy
 msgid "Are you sure you want to delete?"
 msgstr "Are you sure you want to remove this directive?"
@@ -124,7 +130,7 @@ msgstr "Auto-renewal disabled for %{name}"
 msgid "Auto-renewal enabled for %{name}"
 msgstr "Auto-renewal enabled for %{name}"
 
-#: src/views/certificate/CertificateEditor.vue:168
+#: src/views/certificate/CertificateEditor.vue:205
 #: src/views/config/Config.vue:75 src/views/config/ConfigEdit.vue:89
 #: src/views/domain/DomainEdit.vue:261 src/views/nginx_log/NginxLog.vue:170
 msgid "Back"
@@ -166,7 +172,7 @@ msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:260
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:58
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:139
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:143
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:103
 #: src/views/domain/cert/components/ObtainCert.vue:150
 #: src/views/domain/components/Deploy.vue:24
@@ -184,17 +190,17 @@ msgstr "Certificate has expired"
 msgid "Certificate is valid"
 msgstr "Certificate is valid"
 
-#: src/views/certificate/CertificateEditor.vue:119
+#: src/views/certificate/CertificateEditor.vue:127
 #: src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr "Certificate Status"
 
-#: src/routes/index.ts:100 src/views/certificate/Certificate.vue:109
+#: src/routes/index.ts:101 src/views/certificate/Certificate.vue:117
 #, fuzzy
 msgid "Certificates"
 msgstr "Certificate Status"
 
-#: src/routes/index.ts:109
+#: src/routes/index.ts:110
 #, fuzzy
 msgid "Certificates List"
 msgstr "Certificate is valid"
@@ -230,9 +236,17 @@ msgid "Cleaning environment variables"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/Notification/Notification.vue:91
+#: src/views/notification/Notification.vue:78
 msgid "Clear"
 msgstr ""
 
+#: src/components/Notification/Notification.vue:42
+#: src/views/notification/Notification.vue:47
+#, fuzzy
+msgid "Cleared successfully"
+msgstr "Disabled successfully"
+
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/domain/ngx_conf/LocationEditor.vue:121
 #: src/views/domain/ngx_conf/LocationEditor.vue:90
@@ -287,7 +301,7 @@ msgstr "CPU:"
 msgid "Create Another"
 msgstr "Create Another"
 
-#: src/views/user/User.vue:34
+#: src/views/notification/Notification.vue:32 src/views/user/User.vue:34
 msgid "Created at"
 msgstr "Created at"
 
@@ -312,7 +326,7 @@ msgstr ""
 msgid "Custom"
 msgstr ""
 
-#: src/routes/index.ts:51
+#: src/routes/index.ts:52
 msgid "Dashboard"
 msgstr "Dashboard"
 
@@ -320,7 +334,7 @@ msgstr "Dashboard"
 msgid "Database (Optional, default: database)"
 msgstr "Database (Optional, default: database)"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:354
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:360
 #: src/views/domain/DomainList.vue:153
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:259
 msgid "Delete"
@@ -358,6 +372,10 @@ msgstr "Saved successfully"
 msgid "Description"
 msgstr ""
 
+#: src/views/notification/Notification.vue:28
+msgid "Details"
+msgstr ""
+
 #: src/views/system/About.vue:25
 msgid "Development Mode"
 msgstr "Development Mode"
@@ -385,7 +403,7 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "Disable auto-renewal failed for %{name}"
 
 #: src/views/domain/cert/ChangeCert.vue:48 src/views/domain/DomainEdit.vue:190
-#: src/views/domain/DomainList.vue:123
+#: src/views/domain/DomainList.vue:122
 msgid "Disabled"
 msgstr "Disabled"
 
@@ -398,7 +416,7 @@ msgstr "Disabled successfully"
 msgid "Disk IO"
 msgstr "Disk IO"
 
-#: src/routes/index.ts:130 src/views/certificate/DNSCredential.vue:39
+#: src/routes/index.ts:131 src/views/certificate/DNSCredential.vue:39
 msgid "DNS Credentials"
 msgstr ""
 
@@ -446,7 +464,7 @@ msgstr "Are you sure you want to remove this directive?"
 msgid "Domain Config Created Successfully"
 msgstr "Domain Config Created Successfully"
 
-#: src/views/certificate/CertificateEditor.vue:103
+#: src/views/certificate/CertificateEditor.vue:111
 msgid "Domains list is empty, try to reopen Auto Cert for %{config}"
 msgstr ""
 
@@ -463,7 +481,7 @@ msgid "Dry run mode enabled"
 msgstr ""
 
 #: src/views/domain/components/SiteDuplicate.vue:128
-#: src/views/domain/DomainList.vue:139
+#: src/views/domain/DomainList.vue:138
 msgid "Duplicate"
 msgstr ""
 
@@ -491,11 +509,11 @@ msgstr "Saved successfully"
 msgid "Edit %{n}"
 msgstr "Edit %{n}"
 
-#: src/routes/index.ts:92 src/views/config/ConfigEdit.vue:83
+#: src/routes/index.ts:93 src/views/config/ConfigEdit.vue:83
 msgid "Edit Configuration"
 msgstr "Edit Configuration"
 
-#: src/routes/index.ts:74
+#: src/routes/index.ts:75
 msgid "Edit Site"
 msgstr "Edit Site"
 
@@ -536,7 +554,7 @@ msgstr "Enable TLS"
 #: src/views/domain/cert/ChangeCert.vue:44
 #: src/views/domain/components/Deploy.vue:89
 #: src/views/domain/components/RightSettings.vue:75
-#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:131
+#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:130
 msgid "Enabled"
 msgstr "Enabled"
 
@@ -550,7 +568,7 @@ msgstr "Enabled successfully"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Encrypt website with Let's Encrypt"
 
-#: src/routes/index.ts:168 src/views/environment/Environment.vue:113
+#: src/routes/index.ts:169 src/views/environment/Environment.vue:113
 msgid "Environment"
 msgstr ""
 
@@ -559,11 +577,11 @@ msgstr ""
 msgid "Environments"
 msgstr "Comments"
 
-#: src/views/config/InspectConfig.vue:47
+#: src/constants/index.ts:17 src/views/config/InspectConfig.vue:47
 msgid "Error"
 msgstr ""
 
-#: src/routes/index.ts:155 src/views/domain/ngx_conf/LogEntry.vue:86
+#: src/routes/index.ts:156 src/views/domain/ngx_conf/LogEntry.vue:86
 msgid "Error Logs"
 msgstr ""
 
@@ -571,7 +589,7 @@ msgstr ""
 msgid "Executable Path"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:84
+#: src/views/certificate/Certificate.vue:92
 msgid "Expired"
 msgstr ""
 
@@ -637,6 +655,11 @@ msgstr "Save error %{msg}"
 msgid "Format successfully"
 msgstr "Saved successfully"
 
+#: src/views/certificate/Certificate.vue:45
+#, fuzzy
+msgid "General Certificate"
+msgstr "Certificate is valid"
+
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:60
 msgid "Generate"
 msgstr ""
@@ -674,7 +697,7 @@ msgstr ""
 msgid "GPT-4-Turbo"
 msgstr ""
 
-#: src/routes/index.ts:44
+#: src/routes/index.ts:45
 msgid "Home"
 msgstr "Home"
 
@@ -694,6 +717,10 @@ msgstr ""
 msgid "HTTP01"
 msgstr ""
 
+#: src/constants/index.ts:19
+msgid "Info"
+msgstr ""
+
 #: src/language/constants.ts:27
 msgid "Initial core upgrader error"
 msgstr ""
@@ -702,7 +729,7 @@ msgstr ""
 msgid "Initialing core upgrader"
 msgstr ""
 
-#: src/routes/index.ts:211 src/views/other/Install.vue:139
+#: src/routes/index.ts:220 src/views/other/Install.vue:139
 msgid "Install"
 msgstr "Install"
 
@@ -764,12 +791,12 @@ msgstr "Location"
 msgid "Locations"
 msgstr "Locations"
 
-#: src/views/certificate/CertificateEditor.vue:156
+#: src/views/certificate/CertificateEditor.vue:193
 #, fuzzy
 msgid "Log"
 msgstr "Login"
 
-#: src/routes/index.ts:217 src/views/other/Login.vue:147
+#: src/routes/index.ts:226 src/views/other/Login.vue:147
 msgid "Login"
 msgstr "Login"
 
@@ -777,7 +804,7 @@ msgstr "Login"
 msgid "Login successful"
 msgstr "Login successful"
 
-#: src/layouts/HeaderLayout.vue:21
+#: src/layouts/HeaderLayout.vue:22
 msgid "Logout successful"
 msgstr "Logout successful"
 
@@ -790,18 +817,23 @@ msgstr ""
 "Make sure you have configured a reverse proxy for .well-known directory to "
 "HTTPChallengePort (default: 9180) before getting the certificate."
 
-#: src/routes/index.ts:83
+#: src/routes/index.ts:84
 msgid "Manage Configs"
 msgstr "Manage Configs"
 
-#: src/routes/index.ts:58 src/views/domain/DomainList.vue:102
+#: src/routes/index.ts:59 src/views/domain/DomainList.vue:102
 msgid "Manage Sites"
 msgstr "Manage Sites"
 
-#: src/routes/index.ts:176 src/views/user/User.vue:53
+#: src/routes/index.ts:185 src/views/user/User.vue:53
 msgid "Manage Users"
 msgstr "Manage Users"
 
+#: src/views/certificate/Certificate.vue:44
+#, fuzzy
+msgid "Managed Certificate"
+msgstr "Certificate is valid"
+
 #: src/views/dashboard/ServerAnalytic.vue:220
 msgid "Memory"
 msgstr "Memory"
@@ -811,13 +843,13 @@ msgid "Memory and Storage"
 msgstr "Memory and Storage"
 
 #: src/components/ChatGPT/ChatGPT.vue:256
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:336
 #, fuzzy
 msgid "Modify"
 msgstr "Modify Config"
 
-#: src/routes/index.ts:114 src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:115 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Modify Certificate"
 msgstr "Certificate Status"
@@ -832,6 +864,7 @@ msgid "Multi-line Directive"
 msgstr "Single Directive"
 
 #: src/views/certificate/Certificate.vue:22
+#: src/views/certificate/CertificateEditor.vue:145
 #: src/views/certificate/DNSCredential.vue:13 src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:21
 #: src/views/domain/components/RightSettings.vue:81
@@ -886,7 +919,7 @@ msgstr ""
 msgid "Nginx Error Log Path"
 msgstr ""
 
-#: src/routes/index.ts:145 src/views/nginx_log/NginxLog.vue:145
+#: src/routes/index.ts:146 src/views/nginx_log/NginxLog.vue:145
 msgid "Nginx Log"
 msgstr ""
 
@@ -901,10 +934,12 @@ msgid "Nginx restarted successfully"
 msgstr "Saved successfully"
 
 #: src/components/ChatGPT/ChatGPT.vue:277
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:345
-#: src/views/domain/DomainList.vue:143
+#: src/components/Notification/Notification.vue:84
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:351
+#: src/views/domain/DomainList.vue:142
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:81
 #: src/views/domain/ngx_conf/LocationEditor.vue:76
+#: src/views/notification/Notification.vue:71
 msgid "No"
 msgstr "No"
 
@@ -912,11 +947,11 @@ msgstr "No"
 msgid "Node Secret"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:90
+#: src/views/certificate/Certificate.vue:98
 msgid "Not After"
 msgstr ""
 
-#: src/routes/index.ts:223 src/routes/index.ts:225
+#: src/routes/index.ts:232 src/routes/index.ts:234
 msgid "Not Found"
 msgstr "Not Found"
 
@@ -929,6 +964,16 @@ msgstr "Not Valid Before: %{date}"
 msgid "Note"
 msgstr ""
 
+#: src/views/notification/Notification.vue:63
+#, fuzzy
+msgid "Notification"
+msgstr "Certificate is valid"
+
+#: src/components/Notification/Notification.vue:82 src/routes/index.ts:177
+#, fuzzy
+msgid "Notifications"
+msgstr "Certificate is valid"
+
 #: src/views/domain/cert/components/ObtainCert.vue:191
 #, fuzzy
 msgid "Obtain certificate"
@@ -945,16 +990,18 @@ msgid "Offline"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:278
+#: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:59
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:140
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:346
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:144
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:352
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:104
 #: src/views/domain/cert/components/ObtainCert.vue:149
 #: src/views/domain/components/Deploy.vue:23
 #: src/views/domain/components/RightSettings.vue:51
-#: src/views/domain/DomainList.vue:144
+#: src/views/domain/DomainList.vue:143
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:215
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:47
+#: src/views/notification/Notification.vue:72
 msgid "OK"
 msgstr ""
 
@@ -1059,7 +1106,7 @@ msgstr ""
 msgid "Pre-release"
 msgstr ""
 
-#: src/routes/index.ts:184 src/views/preference/Preference.vue:85
+#: src/routes/index.ts:193 src/views/preference/Preference.vue:85
 msgid "Preference"
 msgstr ""
 
@@ -1124,12 +1171,27 @@ msgstr ""
 msgid "Reloading nginx"
 msgstr ""
 
+#: src/components/Notification/Notification.vue:52
+#, fuzzy
+msgid "Removed successfully"
+msgstr "Saved successfully"
+
 #: src/views/certificate/RenewCert.vue:43
 #: src/views/certificate/RenewCert.vue:47
 #, fuzzy
 msgid "Renew Certificate"
 msgstr "Certificate is valid"
 
+#: src/language/constants.ts:38
+#, fuzzy
+msgid "Renew Certificate Error"
+msgstr "Certificate is valid"
+
+#: src/language/constants.ts:37
+#, fuzzy
+msgid "Renew Certificate Success"
+msgstr "Certificate is valid"
+
 #: src/views/certificate/RenewCert.vue:25
 #, fuzzy
 msgid "Renew successfully"
@@ -1161,7 +1223,7 @@ msgid "Running"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:259
-#: src/views/certificate/CertificateEditor.vue:175
+#: src/views/certificate/CertificateEditor.vue:212
 #: src/views/config/ConfigEdit.vue:98 src/views/domain/DomainEdit.vue:268
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:111
 #: src/views/preference/Preference.vue:113
@@ -1178,7 +1240,7 @@ msgid "Save error %{msg}"
 msgstr "Save error %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/views/certificate/CertificateEditor.vue:46
+#: src/views/certificate/CertificateEditor.vue:47
 #: src/views/preference/Preference.vue:58
 #, fuzzy
 msgid "Save successfully"
@@ -1245,31 +1307,33 @@ msgstr ""
 msgid "Single Directive"
 msgstr "Single Directive"
 
-#: src/routes/index.ts:159
+#: src/routes/index.ts:160
 #, fuzzy
 msgid "Site Logs"
 msgstr "Sites List"
 
-#: src/routes/index.ts:66
+#: src/routes/index.ts:67
 msgid "Sites List"
 msgstr "Sites List"
 
-#: src/views/certificate/CertificateEditor.vue:135
+#: src/views/certificate/CertificateEditor.vue:172
 #, fuzzy
 msgid "SSL Certificate Content"
 msgstr "Certificate Status"
 
-#: src/views/certificate/CertificateEditor.vue:142
+#: src/views/certificate/CertificateEditor.vue:179
 #, fuzzy
 msgid "SSL Certificate Key Content"
 msgstr "Certificate Status"
 
-#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/Certificate.vue:71
+#: src/views/certificate/CertificateEditor.vue:163
 #, fuzzy
 msgid "SSL Certificate Key Path"
 msgstr "Certificate Status"
 
-#: src/views/certificate/Certificate.vue:55
+#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/CertificateEditor.vue:154
 #, fuzzy
 msgid "SSL Certificate Path"
 msgstr "Certificate Status"
@@ -1284,7 +1348,7 @@ msgstr "Login"
 msgid "Stable"
 msgstr "Enabled"
 
-#: src/views/certificate/Certificate.vue:71 src/views/domain/DomainList.vue:25
+#: src/views/certificate/Certificate.vue:79 src/views/domain/DomainList.vue:25
 #: src/views/environment/Environment.vue:78
 msgid "Status"
 msgstr "Status"
@@ -1302,6 +1366,10 @@ msgstr "Storage"
 msgid "Subject Name: %{subject}"
 msgstr "Subject Name: %{name}"
 
+#: src/constants/index.ts:20
+msgid "Success"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:233
 msgid "Swap"
 msgstr "Swap"
@@ -1314,7 +1382,7 @@ msgstr ""
 msgid "Switch to light theme"
 msgstr ""
 
-#: src/routes/index.ts:192
+#: src/routes/index.ts:201
 msgid "System"
 msgstr ""
 
@@ -1327,7 +1395,7 @@ msgstr "Enabled"
 msgid "Target"
 msgstr ""
 
-#: src/routes/index.ts:137 src/views/pty/Terminal.vue:95
+#: src/routes/index.ts:138 src/views/pty/Terminal.vue:95
 msgid "Terminal"
 msgstr "Terminal"
 
@@ -1361,11 +1429,11 @@ msgstr ""
 msgid "The username or password is incorrect"
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:93
+#: src/views/certificate/CertificateEditor.vue:101
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:83
+#: src/views/certificate/CertificateEditor.vue:91
 msgid "This certificate is managed by Nginx UI"
 msgstr ""
 
@@ -1373,6 +1441,10 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr ""
 
+#: src/views/notification/Notification.vue:21
+msgid "Title"
+msgstr ""
+
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:42
 msgid ""
 "To make sure the certification auto-renewal can work normally, we need to "
@@ -1382,6 +1454,7 @@ msgid ""
 msgstr ""
 
 #: src/views/certificate/Certificate.vue:39 src/views/config/config.ts:14
+#: src/views/notification/Notification.vue:15
 msgid "Type"
 msgstr ""
 
@@ -1398,7 +1471,7 @@ msgstr "Updated at"
 msgid "Updated successfully"
 msgstr "Saved successfully"
 
-#: src/routes/index.ts:203 src/views/system/Upgrade.vue:143
+#: src/routes/index.ts:212 src/views/system/Upgrade.vue:143
 #: src/views/system/Upgrade.vue:235
 msgid "Upgrade"
 msgstr ""
@@ -1433,7 +1506,7 @@ msgstr "Username"
 msgid "Username (*)"
 msgstr "Username (*)"
 
-#: src/views/certificate/Certificate.vue:80
+#: src/views/certificate/Certificate.vue:88
 msgid "Valid"
 msgstr ""
 
@@ -1441,7 +1514,12 @@ msgstr ""
 msgid "View"
 msgstr ""
 
-#: src/views/config/InspectConfig.vue:36
+#: src/components/Notification/Notification.vue:143
+#, fuzzy
+msgid "View all notifications"
+msgstr "Certificate is valid"
+
+#: src/constants/index.ts:18 src/views/config/InspectConfig.vue:36
 #: src/views/domain/cert/components/AutoCertStepOne.vue:23
 #: src/views/domain/DomainAdd.vue:114
 msgid "Warning"
@@ -1493,10 +1571,6 @@ msgstr ""
 #~ msgid "Config Name"
 #~ msgstr "Configuration Name"
 
-#, fuzzy
-#~ msgid "Certification"
-#~ msgstr "Certificate is valid"
-
 #, fuzzy
 #~ msgid "Inspect Configurations"
 #~ msgstr "Edit Configuration"

+ 143 - 68
app/src/language/es/app.po

@@ -14,30 +14,30 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=n != 1;\n"
 "X-Generator: Weblate 5.0\n"
 
-#: src/routes/index.ts:199
+#: src/routes/index.ts:208
 msgid "About"
 msgstr "Acerca de"
 
-#: src/routes/index.ts:151 src/views/domain/ngx_conf/LogEntry.vue:78
+#: src/routes/index.ts:152 src/views/domain/ngx_conf/LogEntry.vue:78
 msgid "Access Logs"
 msgstr "Registros de acceso"
 
-#: src/views/certificate/Certificate.vue:96
+#: src/views/certificate/Certificate.vue:104
 #: src/views/certificate/DNSCredential.vue:32 src/views/config/config.ts:36
 #: src/views/domain/DomainList.vue:50 src/views/environment/Environment.vue:105
-#: src/views/user/User.vue:46
+#: src/views/notification/Notification.vue:38 src/views/user/User.vue:46
 msgid "Action"
 msgstr "Acción"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:114
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:115
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
+#: src/views/certificate/Certificate.vue:120
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:115
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:308
 msgid "Add"
 msgstr "Agregar"
 
-#: src/routes/index.ts:122 src/views/certificate/Certificate.vue:115
-#: src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:123 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Add Certificate"
 msgstr "Estado del Certificado"
@@ -51,7 +51,7 @@ msgstr "Añadir directiva a continuación"
 msgid "Add Location"
 msgstr "Agregar Ubicación"
 
-#: src/routes/index.ts:70 src/views/domain/DomainAdd.vue:91
+#: src/routes/index.ts:71 src/views/domain/DomainAdd.vue:91
 msgid "Add Site"
 msgstr "Agregar Sitio"
 
@@ -79,12 +79,18 @@ msgstr "Token de la API"
 msgid "Arch"
 msgstr "Arquitectura"
 
+#: src/components/Notification/Notification.vue:86
+#: src/views/notification/Notification.vue:73
+#, fuzzy
+msgid "Are you sure you want to clear all notifications?"
+msgstr "¿Está seguro de que desea borrar el registro del chat?"
+
 #: src/components/ChatGPT/ChatGPT.vue:279
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "¿Está seguro de que desea borrar el registro del chat?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:347
-#: src/views/domain/DomainList.vue:145
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:353
+#: src/views/domain/DomainList.vue:144
 msgid "Are you sure you want to delete?"
 msgstr "¿Está seguro de que quiere borrar?"
 
@@ -125,7 +131,7 @@ msgstr "Renovación automática deshabilitada por %{name}"
 msgid "Auto-renewal enabled for %{name}"
 msgstr "Renovación automática habilitada por %{name}"
 
-#: src/views/certificate/CertificateEditor.vue:168
+#: src/views/certificate/CertificateEditor.vue:205
 #: src/views/config/Config.vue:75 src/views/config/ConfigEdit.vue:89
 #: src/views/domain/DomainEdit.vue:261 src/views/nginx_log/NginxLog.vue:170
 msgid "Back"
@@ -164,7 +170,7 @@ msgstr "Directorio CA"
 
 #: src/components/ChatGPT/ChatGPT.vue:260
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:58
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:139
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:143
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:103
 #: src/views/domain/cert/components/ObtainCert.vue:150
 #: src/views/domain/components/Deploy.vue:24
@@ -182,17 +188,17 @@ msgstr "El certificado expiró"
 msgid "Certificate is valid"
 msgstr "El certificado es válido"
 
-#: src/views/certificate/CertificateEditor.vue:119
+#: src/views/certificate/CertificateEditor.vue:127
 #: src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr "Estado del Certificado"
 
-#: src/routes/index.ts:100 src/views/certificate/Certificate.vue:109
+#: src/routes/index.ts:101 src/views/certificate/Certificate.vue:117
 #, fuzzy
 msgid "Certificates"
 msgstr "Estado del Certificado"
 
-#: src/routes/index.ts:109
+#: src/routes/index.ts:110
 #, fuzzy
 msgid "Certificates List"
 msgstr "Lista de Certificados"
@@ -227,9 +233,17 @@ msgid "Cleaning environment variables"
 msgstr "Borrar las variables de entorno"
 
 #: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/Notification/Notification.vue:91
+#: src/views/notification/Notification.vue:78
 msgid "Clear"
 msgstr "Limpiar"
 
+#: src/components/Notification/Notification.vue:42
+#: src/views/notification/Notification.vue:47
+#, fuzzy
+msgid "Cleared successfully"
+msgstr "Desactivado con éxito"
+
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/domain/ngx_conf/LocationEditor.vue:121
 #: src/views/domain/ngx_conf/LocationEditor.vue:90
@@ -283,7 +297,7 @@ msgstr "CPU:"
 msgid "Create Another"
 msgstr "Crear otro"
 
-#: src/views/user/User.vue:34
+#: src/views/notification/Notification.vue:32 src/views/user/User.vue:34
 msgid "Created at"
 msgstr "Creado el"
 
@@ -308,7 +322,7 @@ msgstr "Versión actual"
 msgid "Custom"
 msgstr "Personalizado"
 
-#: src/routes/index.ts:51
+#: src/routes/index.ts:52
 msgid "Dashboard"
 msgstr "Panel"
 
@@ -316,7 +330,7 @@ msgstr "Panel"
 msgid "Database (Optional, default: database)"
 msgstr "Base de datos (Opcional, default: database)"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:354
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:360
 #: src/views/domain/DomainList.vue:153
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:259
 msgid "Delete"
@@ -353,6 +367,10 @@ msgstr "Desplegado con éxito"
 msgid "Description"
 msgstr "Descripción"
 
+#: src/views/notification/Notification.vue:28
+msgid "Details"
+msgstr ""
+
 #: src/views/system/About.vue:25
 msgid "Development Mode"
 msgstr "Modo de desarrollo"
@@ -380,7 +398,7 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "No se pudo desactivar la renovación automática por %{name}"
 
 #: src/views/domain/cert/ChangeCert.vue:48 src/views/domain/DomainEdit.vue:190
-#: src/views/domain/DomainList.vue:123
+#: src/views/domain/DomainList.vue:122
 msgid "Disabled"
 msgstr "Desactivado"
 
@@ -393,7 +411,7 @@ msgstr "Desactivado con éxito"
 msgid "Disk IO"
 msgstr "I/O del disco"
 
-#: src/routes/index.ts:130 src/views/certificate/DNSCredential.vue:39
+#: src/routes/index.ts:131 src/views/certificate/DNSCredential.vue:39
 msgid "DNS Credentials"
 msgstr "Credenciales de DNS"
 
@@ -436,7 +454,7 @@ msgstr "¿Quieres eliminar este servidor?"
 msgid "Domain Config Created Successfully"
 msgstr "Configuración de dominio creada con éxito"
 
-#: src/views/certificate/CertificateEditor.vue:103
+#: src/views/certificate/CertificateEditor.vue:111
 #, fuzzy
 msgid "Domains list is empty, try to reopen Auto Cert for %{config}"
 msgstr ""
@@ -456,7 +474,7 @@ msgid "Dry run mode enabled"
 msgstr "Modo de ejecución de prueba habilitado"
 
 #: src/views/domain/components/SiteDuplicate.vue:128
-#: src/views/domain/DomainList.vue:139
+#: src/views/domain/DomainList.vue:138
 msgid "Duplicate"
 msgstr "Duplicar"
 
@@ -480,11 +498,11 @@ msgstr "Duplicado con éxito a local"
 msgid "Edit %{n}"
 msgstr "Editar %{n}"
 
-#: src/routes/index.ts:92 src/views/config/ConfigEdit.vue:83
+#: src/routes/index.ts:93 src/views/config/ConfigEdit.vue:83
 msgid "Edit Configuration"
 msgstr "Editar Configuración"
 
-#: src/routes/index.ts:74
+#: src/routes/index.ts:75
 msgid "Edit Site"
 msgstr "Editar Sitio"
 
@@ -524,7 +542,7 @@ msgstr "Habilitar TLS"
 #: src/views/domain/cert/ChangeCert.vue:44
 #: src/views/domain/components/Deploy.vue:89
 #: src/views/domain/components/RightSettings.vue:75
-#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:131
+#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:130
 msgid "Enabled"
 msgstr "Habilitado"
 
@@ -538,7 +556,7 @@ msgstr "Habilitado con éxito"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Encriptar sitio web con Let's Encrypt"
 
-#: src/routes/index.ts:168 src/views/environment/Environment.vue:113
+#: src/routes/index.ts:169 src/views/environment/Environment.vue:113
 msgid "Environment"
 msgstr "Entorno"
 
@@ -546,11 +564,11 @@ msgstr "Entorno"
 msgid "Environments"
 msgstr "Entornos"
 
-#: src/views/config/InspectConfig.vue:47
+#: src/constants/index.ts:17 src/views/config/InspectConfig.vue:47
 msgid "Error"
 msgstr "Error"
 
-#: src/routes/index.ts:155 src/views/domain/ngx_conf/LogEntry.vue:86
+#: src/routes/index.ts:156 src/views/domain/ngx_conf/LogEntry.vue:86
 msgid "Error Logs"
 msgstr "Registros de acceso"
 
@@ -558,7 +576,7 @@ msgstr "Registros de acceso"
 msgid "Executable Path"
 msgstr "Ruta ejecutable"
 
-#: src/views/certificate/Certificate.vue:84
+#: src/views/certificate/Certificate.vue:92
 msgid "Expired"
 msgstr ""
 
@@ -623,6 +641,11 @@ msgstr "Error de formato %{msg}"
 msgid "Format successfully"
 msgstr "Formateado correctamente"
 
+#: src/views/certificate/Certificate.vue:45
+#, fuzzy
+msgid "General Certificate"
+msgstr "Cambiar Certificado"
+
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:60
 msgid "Generate"
 msgstr "Generar"
@@ -660,7 +683,7 @@ msgstr "GPT-4-32K"
 msgid "GPT-4-Turbo"
 msgstr "GPT-3.5-Turbo"
 
-#: src/routes/index.ts:44
+#: src/routes/index.ts:45
 msgid "Home"
 msgstr "Inicio"
 
@@ -680,6 +703,10 @@ msgstr "Puerto HTTP"
 msgid "HTTP01"
 msgstr "HTTP01"
 
+#: src/constants/index.ts:19
+msgid "Info"
+msgstr ""
+
 #: src/language/constants.ts:27
 msgid "Initial core upgrader error"
 msgstr "Error de actualización de kernel inicial"
@@ -688,7 +715,7 @@ msgstr "Error de actualización de kernel inicial"
 msgid "Initialing core upgrader"
 msgstr "Inicializando la actualización del kernel"
 
-#: src/routes/index.ts:211 src/views/other/Install.vue:139
+#: src/routes/index.ts:220 src/views/other/Install.vue:139
 msgid "Install"
 msgstr "Instalar"
 
@@ -747,12 +774,12 @@ msgstr "Ubicación"
 msgid "Locations"
 msgstr "Ubicaciones"
 
-#: src/views/certificate/CertificateEditor.vue:156
+#: src/views/certificate/CertificateEditor.vue:193
 #, fuzzy
 msgid "Log"
 msgstr "Acceso"
 
-#: src/routes/index.ts:217 src/views/other/Login.vue:147
+#: src/routes/index.ts:226 src/views/other/Login.vue:147
 msgid "Login"
 msgstr "Acceso"
 
@@ -760,7 +787,7 @@ msgstr "Acceso"
 msgid "Login successful"
 msgstr "Acceso exitoso"
 
-#: src/layouts/HeaderLayout.vue:21
+#: src/layouts/HeaderLayout.vue:22
 msgid "Logout successful"
 msgstr "Cierre de sesión exitoso"
 
@@ -772,18 +799,23 @@ msgstr ""
 "Asegúrese de haber configurado un proxy reverso para el directorio .well-"
 "known en HTTPChallengePort antes de obtener el certificado."
 
-#: src/routes/index.ts:83
+#: src/routes/index.ts:84
 msgid "Manage Configs"
 msgstr "Administrar configuraciones"
 
-#: src/routes/index.ts:58 src/views/domain/DomainList.vue:102
+#: src/routes/index.ts:59 src/views/domain/DomainList.vue:102
 msgid "Manage Sites"
 msgstr "Administrar sitios"
 
-#: src/routes/index.ts:176 src/views/user/User.vue:53
+#: src/routes/index.ts:185 src/views/user/User.vue:53
 msgid "Manage Users"
 msgstr "Administrar usuarios"
 
+#: src/views/certificate/Certificate.vue:44
+#, fuzzy
+msgid "Managed Certificate"
+msgstr "Cambiar Certificado"
+
 #: src/views/dashboard/ServerAnalytic.vue:220
 msgid "Memory"
 msgstr "Memoria"
@@ -793,12 +825,12 @@ msgid "Memory and Storage"
 msgstr "Memoria y almacenamiento"
 
 #: src/components/ChatGPT/ChatGPT.vue:256
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:336
 msgid "Modify"
 msgstr "Modificar"
 
-#: src/routes/index.ts:114 src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:115 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Modify Certificate"
 msgstr "Estado del Certificado"
@@ -812,6 +844,7 @@ msgid "Multi-line Directive"
 msgstr "Directiva multilínea"
 
 #: src/views/certificate/Certificate.vue:22
+#: src/views/certificate/CertificateEditor.vue:145
 #: src/views/certificate/DNSCredential.vue:13 src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:21
 #: src/views/domain/components/RightSettings.vue:81
@@ -865,7 +898,7 @@ msgstr "Control de Nginx"
 msgid "Nginx Error Log Path"
 msgstr "Ruta de registro de errores de Nginx"
 
-#: src/routes/index.ts:145 src/views/nginx_log/NginxLog.vue:145
+#: src/routes/index.ts:146 src/views/nginx_log/NginxLog.vue:145
 msgid "Nginx Log"
 msgstr "Registro Nginx"
 
@@ -878,10 +911,12 @@ msgid "Nginx restarted successfully"
 msgstr "Nginx reiniciado con éxito"
 
 #: src/components/ChatGPT/ChatGPT.vue:277
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:345
-#: src/views/domain/DomainList.vue:143
+#: src/components/Notification/Notification.vue:84
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:351
+#: src/views/domain/DomainList.vue:142
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:81
 #: src/views/domain/ngx_conf/LocationEditor.vue:76
+#: src/views/notification/Notification.vue:71
 msgid "No"
 msgstr "No"
 
@@ -889,11 +924,11 @@ msgstr "No"
 msgid "Node Secret"
 msgstr "Secreto del nodo"
 
-#: src/views/certificate/Certificate.vue:90
+#: src/views/certificate/Certificate.vue:98
 msgid "Not After"
 msgstr ""
 
-#: src/routes/index.ts:223 src/routes/index.ts:225
+#: src/routes/index.ts:232 src/routes/index.ts:234
 msgid "Not Found"
 msgstr "No encontrado"
 
@@ -906,6 +941,16 @@ msgstr "No válido antes: %{date}"
 msgid "Note"
 msgstr "Nota"
 
+#: src/views/notification/Notification.vue:63
+#, fuzzy
+msgid "Notification"
+msgstr "Certificación"
+
+#: src/components/Notification/Notification.vue:82 src/routes/index.ts:177
+#, fuzzy
+msgid "Notifications"
+msgstr "Certificación"
+
 #: src/views/domain/cert/components/ObtainCert.vue:191
 msgid "Obtain certificate"
 msgstr "Obtener certificado"
@@ -921,16 +966,18 @@ msgid "Offline"
 msgstr "Desconectado"
 
 #: src/components/ChatGPT/ChatGPT.vue:278
+#: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:59
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:140
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:346
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:144
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:352
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:104
 #: src/views/domain/cert/components/ObtainCert.vue:149
 #: src/views/domain/components/Deploy.vue:23
 #: src/views/domain/components/RightSettings.vue:51
-#: src/views/domain/DomainList.vue:144
+#: src/views/domain/DomainList.vue:143
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:215
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:47
+#: src/views/notification/Notification.vue:72
 msgid "OK"
 msgstr "OK"
 
@@ -1040,7 +1087,7 @@ msgstr "¡Seleccione al menos un nodo!"
 msgid "Pre-release"
 msgstr "Prelanzamiento"
 
-#: src/routes/index.ts:184 src/views/preference/Preference.vue:85
+#: src/routes/index.ts:193 src/views/preference/Preference.vue:85
 msgid "Preference"
 msgstr "Configuración"
 
@@ -1103,12 +1150,27 @@ msgstr "Recargando"
 msgid "Reloading nginx"
 msgstr "Recargando Nginx"
 
+#: src/components/Notification/Notification.vue:52
+#, fuzzy
+msgid "Removed successfully"
+msgstr "Guardado con éxito"
+
 #: src/views/certificate/RenewCert.vue:43
 #: src/views/certificate/RenewCert.vue:47
 #, fuzzy
 msgid "Renew Certificate"
 msgstr "Cambiar Certificado"
 
+#: src/language/constants.ts:38
+#, fuzzy
+msgid "Renew Certificate Error"
+msgstr "Cambiar Certificado"
+
+#: src/language/constants.ts:37
+#, fuzzy
+msgid "Renew Certificate Success"
+msgstr "Cambiar Certificado"
+
 #: src/views/certificate/RenewCert.vue:25
 #, fuzzy
 msgid "Renew successfully"
@@ -1139,7 +1201,7 @@ msgid "Running"
 msgstr "Corriendo"
 
 #: src/components/ChatGPT/ChatGPT.vue:259
-#: src/views/certificate/CertificateEditor.vue:175
+#: src/views/certificate/CertificateEditor.vue:212
 #: src/views/config/ConfigEdit.vue:98 src/views/domain/DomainEdit.vue:268
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:111
 #: src/views/preference/Preference.vue:113
@@ -1156,7 +1218,7 @@ msgid "Save error %{msg}"
 msgstr "Error al guardar %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/views/certificate/CertificateEditor.vue:46
+#: src/views/certificate/CertificateEditor.vue:47
 #: src/views/preference/Preference.vue:58
 msgid "Save successfully"
 msgstr "Guardado con éxito"
@@ -1223,29 +1285,31 @@ msgstr "Usando el proveedor de desafíos HTTP01"
 msgid "Single Directive"
 msgstr "Directiva de una sola línea"
 
-#: src/routes/index.ts:159
+#: src/routes/index.ts:160
 msgid "Site Logs"
 msgstr "Registros del sitio"
 
-#: src/routes/index.ts:66
+#: src/routes/index.ts:67
 msgid "Sites List"
 msgstr "Lista de sitios"
 
-#: src/views/certificate/CertificateEditor.vue:135
+#: src/views/certificate/CertificateEditor.vue:172
 #, fuzzy
 msgid "SSL Certificate Content"
 msgstr "Contenido de certificado SSL"
 
-#: src/views/certificate/CertificateEditor.vue:142
+#: src/views/certificate/CertificateEditor.vue:179
 #, fuzzy
 msgid "SSL Certificate Key Content"
 msgstr "Contenido de la llave del certificado SSL"
 
-#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/Certificate.vue:71
+#: src/views/certificate/CertificateEditor.vue:163
 msgid "SSL Certificate Key Path"
 msgstr "Ruta de la llave del certificado SSL"
 
-#: src/views/certificate/Certificate.vue:55
+#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/CertificateEditor.vue:154
 msgid "SSL Certificate Path"
 msgstr "Ruta del certificado SSL"
 
@@ -1258,7 +1322,7 @@ msgstr "Acceso"
 msgid "Stable"
 msgstr "Estable"
 
-#: src/views/certificate/Certificate.vue:71 src/views/domain/DomainList.vue:25
+#: src/views/certificate/Certificate.vue:79 src/views/domain/DomainList.vue:25
 #: src/views/environment/Environment.vue:78
 msgid "Status"
 msgstr "Estado"
@@ -1276,6 +1340,10 @@ msgstr "Almacenamiento"
 msgid "Subject Name: %{subject}"
 msgstr "Nombre del asunto: %{name}"
 
+#: src/constants/index.ts:20
+msgid "Success"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:233
 msgid "Swap"
 msgstr "Swap"
@@ -1288,7 +1356,7 @@ msgstr ""
 msgid "Switch to light theme"
 msgstr ""
 
-#: src/routes/index.ts:192
+#: src/routes/index.ts:201
 msgid "System"
 msgstr "Sistema"
 
@@ -1300,7 +1368,7 @@ msgstr "Tabla"
 msgid "Target"
 msgstr "Objetivo"
 
-#: src/routes/index.ts:137 src/views/pty/Terminal.vue:95
+#: src/routes/index.ts:138 src/views/pty/Terminal.vue:95
 msgid "Terminal"
 msgstr "Terminal"
 
@@ -1335,12 +1403,12 @@ msgstr ""
 msgid "The username or password is incorrect"
 msgstr "El nombre de usuario o contraseña son incorrectos"
 
-#: src/views/certificate/CertificateEditor.vue:93
+#: src/views/certificate/CertificateEditor.vue:101
 #, fuzzy
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "Este elemento de auto-cert es inválido, elimínelo por favor."
 
-#: src/views/certificate/CertificateEditor.vue:83
+#: src/views/certificate/CertificateEditor.vue:91
 msgid "This certificate is managed by Nginx UI"
 msgstr ""
 
@@ -1348,6 +1416,10 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr "Este campo no debe estar vacío"
 
+#: src/views/notification/Notification.vue:21
+msgid "Title"
+msgstr ""
+
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:42
 msgid ""
 "To make sure the certification auto-renewal can work normally, we need to "
@@ -1361,6 +1433,7 @@ msgstr ""
 "Nginx. ¿Estás seguro de que quieres continuar?"
 
 #: src/views/certificate/Certificate.vue:39 src/views/config/config.ts:14
+#: src/views/notification/Notification.vue:15
 msgid "Type"
 msgstr "Tipo"
 
@@ -1376,7 +1449,7 @@ msgstr "Actualizado a"
 msgid "Updated successfully"
 msgstr "Actualización exitosa"
 
-#: src/routes/index.ts:203 src/views/system/Upgrade.vue:143
+#: src/routes/index.ts:212 src/views/system/Upgrade.vue:143
 #: src/views/system/Upgrade.vue:235
 msgid "Upgrade"
 msgstr "Actualizar"
@@ -1409,7 +1482,7 @@ msgstr "Nombre de usuario"
 msgid "Username (*)"
 msgstr "Nombre de usuario (*)"
 
-#: src/views/certificate/Certificate.vue:80
+#: src/views/certificate/Certificate.vue:88
 msgid "Valid"
 msgstr ""
 
@@ -1417,7 +1490,12 @@ msgstr ""
 msgid "View"
 msgstr "Ver"
 
-#: src/views/config/InspectConfig.vue:36
+#: src/components/Notification/Notification.vue:143
+#, fuzzy
+msgid "View all notifications"
+msgstr "Certificación"
+
+#: src/constants/index.ts:18 src/views/config/InspectConfig.vue:36
 #: src/views/domain/cert/components/AutoCertStepOne.vue:23
 #: src/views/domain/DomainAdd.vue:114
 msgid "Warning"
@@ -1498,9 +1576,6 @@ msgstr "Puede consultar la actualización de Nginx UI en esta página."
 #~ "verificación, se eliminarán los registros. Tenga en cuenta que las "
 #~ "configuraciones de tiempo que aparecen debajo están todas en segundos."
 
-#~ msgid "Certification"
-#~ msgstr "Certificación"
-
 #~ msgid "Delete ID: %{id}"
 #~ msgstr "Eliminar ID: %{id}"
 

+ 143 - 68
app/src/language/fr_FR/app.po

@@ -11,30 +11,30 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "X-Generator: Poedit 3.3\n"
 
-#: src/routes/index.ts:199
+#: src/routes/index.ts:208
 msgid "About"
 msgstr "À propos"
 
-#: src/routes/index.ts:151 src/views/domain/ngx_conf/LogEntry.vue:78
+#: src/routes/index.ts:152 src/views/domain/ngx_conf/LogEntry.vue:78
 msgid "Access Logs"
 msgstr "Journaux d'accès"
 
-#: src/views/certificate/Certificate.vue:96
+#: src/views/certificate/Certificate.vue:104
 #: src/views/certificate/DNSCredential.vue:32 src/views/config/config.ts:36
 #: src/views/domain/DomainList.vue:50 src/views/environment/Environment.vue:105
-#: src/views/user/User.vue:46
+#: src/views/notification/Notification.vue:38 src/views/user/User.vue:46
 msgid "Action"
 msgstr "Action"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:114
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:115
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
+#: src/views/certificate/Certificate.vue:120
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:115
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:308
 msgid "Add"
 msgstr "Ajouter"
 
-#: src/routes/index.ts:122 src/views/certificate/Certificate.vue:115
-#: src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:123 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Add Certificate"
 msgstr "État du certificat"
@@ -48,7 +48,7 @@ msgstr "Ajouter une directive"
 msgid "Add Location"
 msgstr "Ajouter une localisation"
 
-#: src/routes/index.ts:70 src/views/domain/DomainAdd.vue:91
+#: src/routes/index.ts:71 src/views/domain/DomainAdd.vue:91
 msgid "Add Site"
 msgstr "Ajouter un site"
 
@@ -78,12 +78,18 @@ msgstr "Jeton d'API"
 msgid "Arch"
 msgstr "Arch"
 
+#: src/components/Notification/Notification.vue:86
+#: src/views/notification/Notification.vue:73
+#, fuzzy
+msgid "Are you sure you want to clear all notifications?"
+msgstr "Voulez-vous vraiment effacer l'historique du chat ?"
+
 #: src/components/ChatGPT/ChatGPT.vue:279
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Voulez-vous vraiment effacer l'historique du chat ?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:347
-#: src/views/domain/DomainList.vue:145
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:353
+#: src/views/domain/DomainList.vue:144
 msgid "Are you sure you want to delete?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 
@@ -125,7 +131,7 @@ msgstr "Renouvellement automatique désactivé pour %{name}"
 msgid "Auto-renewal enabled for %{name}"
 msgstr "Renouvellement automatique activé pour %{name}"
 
-#: src/views/certificate/CertificateEditor.vue:168
+#: src/views/certificate/CertificateEditor.vue:205
 #: src/views/config/Config.vue:75 src/views/config/ConfigEdit.vue:89
 #: src/views/domain/DomainEdit.vue:261 src/views/nginx_log/NginxLog.vue:170
 msgid "Back"
@@ -165,7 +171,7 @@ msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:260
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:58
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:139
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:143
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:103
 #: src/views/domain/cert/components/ObtainCert.vue:150
 #: src/views/domain/components/Deploy.vue:24
@@ -183,17 +189,17 @@ msgstr "Le certificat a expiré"
 msgid "Certificate is valid"
 msgstr "Le certificat est valide"
 
-#: src/views/certificate/CertificateEditor.vue:119
+#: src/views/certificate/CertificateEditor.vue:127
 #: src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr "État du certificat"
 
-#: src/routes/index.ts:100 src/views/certificate/Certificate.vue:109
+#: src/routes/index.ts:101 src/views/certificate/Certificate.vue:117
 #, fuzzy
 msgid "Certificates"
 msgstr "État du certificat"
 
-#: src/routes/index.ts:109
+#: src/routes/index.ts:110
 #, fuzzy
 msgid "Certificates List"
 msgstr "Liste des certifications"
@@ -228,9 +234,17 @@ msgid "Cleaning environment variables"
 msgstr "Nettoyage des variables d'environnement"
 
 #: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/Notification/Notification.vue:91
+#: src/views/notification/Notification.vue:78
 msgid "Clear"
 msgstr "Effacer"
 
+#: src/components/Notification/Notification.vue:42
+#: src/views/notification/Notification.vue:47
+#, fuzzy
+msgid "Cleared successfully"
+msgstr "Désactivé avec succès"
+
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/domain/ngx_conf/LocationEditor.vue:121
 #: src/views/domain/ngx_conf/LocationEditor.vue:90
@@ -284,7 +298,7 @@ msgstr "CPU :"
 msgid "Create Another"
 msgstr "Créer un autre"
 
-#: src/views/user/User.vue:34
+#: src/views/notification/Notification.vue:32 src/views/user/User.vue:34
 msgid "Created at"
 msgstr "Créé le"
 
@@ -309,7 +323,7 @@ msgstr "Version actuelle"
 msgid "Custom"
 msgstr "Custom"
 
-#: src/routes/index.ts:51
+#: src/routes/index.ts:52
 msgid "Dashboard"
 msgstr "Dashboard"
 
@@ -317,7 +331,7 @@ msgstr "Dashboard"
 msgid "Database (Optional, default: database)"
 msgstr "Base de données (Facultatif, par défaut : database)"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:354
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:360
 #: src/views/domain/DomainList.vue:153
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:259
 msgid "Delete"
@@ -355,6 +369,10 @@ msgstr "Sauvegarde réussie"
 msgid "Description"
 msgstr "Description"
 
+#: src/views/notification/Notification.vue:28
+msgid "Details"
+msgstr ""
+
 #: src/views/system/About.vue:25
 msgid "Development Mode"
 msgstr "Mode développement"
@@ -382,7 +400,7 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "La désactivation du renouvellement automatique a échoué pour %{name}"
 
 #: src/views/domain/cert/ChangeCert.vue:48 src/views/domain/DomainEdit.vue:190
-#: src/views/domain/DomainList.vue:123
+#: src/views/domain/DomainList.vue:122
 msgid "Disabled"
 msgstr "Désactivé"
 
@@ -395,7 +413,7 @@ msgstr "Désactivé avec succès"
 msgid "Disk IO"
 msgstr "E/S disque"
 
-#: src/routes/index.ts:130 src/views/certificate/DNSCredential.vue:39
+#: src/routes/index.ts:131 src/views/certificate/DNSCredential.vue:39
 msgid "DNS Credentials"
 msgstr "Identifiants DNS"
 
@@ -439,7 +457,7 @@ msgstr "Voulez-vous supprimer ce serveur ?"
 msgid "Domain Config Created Successfully"
 msgstr "La configuration du domaine a été créée avec succès"
 
-#: src/views/certificate/CertificateEditor.vue:103
+#: src/views/certificate/CertificateEditor.vue:111
 #, fuzzy
 msgid "Domains list is empty, try to reopen Auto Cert for %{config}"
 msgstr ""
@@ -459,7 +477,7 @@ msgid "Dry run mode enabled"
 msgstr ""
 
 #: src/views/domain/components/SiteDuplicate.vue:128
-#: src/views/domain/DomainList.vue:139
+#: src/views/domain/DomainList.vue:138
 msgid "Duplicate"
 msgstr "Dupliquer"
 
@@ -487,11 +505,11 @@ msgstr "Dupliqué avec succès"
 msgid "Edit %{n}"
 msgstr "Modifier %{n}"
 
-#: src/routes/index.ts:92 src/views/config/ConfigEdit.vue:83
+#: src/routes/index.ts:93 src/views/config/ConfigEdit.vue:83
 msgid "Edit Configuration"
 msgstr "Modifier la configuration"
 
-#: src/routes/index.ts:74
+#: src/routes/index.ts:75
 msgid "Edit Site"
 msgstr "Modifier le site"
 
@@ -532,7 +550,7 @@ msgstr "Activer TLS"
 #: src/views/domain/cert/ChangeCert.vue:44
 #: src/views/domain/components/Deploy.vue:89
 #: src/views/domain/components/RightSettings.vue:75
-#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:131
+#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:130
 msgid "Enabled"
 msgstr "Activé"
 
@@ -546,7 +564,7 @@ msgstr "Activé avec succès"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Crypter le site Web avec Let's Encrypt"
 
-#: src/routes/index.ts:168 src/views/environment/Environment.vue:113
+#: src/routes/index.ts:169 src/views/environment/Environment.vue:113
 msgid "Environment"
 msgstr ""
 
@@ -555,11 +573,11 @@ msgstr ""
 msgid "Environments"
 msgstr "Commentaires"
 
-#: src/views/config/InspectConfig.vue:47
+#: src/constants/index.ts:17 src/views/config/InspectConfig.vue:47
 msgid "Error"
 msgstr "Erreur"
 
-#: src/routes/index.ts:155 src/views/domain/ngx_conf/LogEntry.vue:86
+#: src/routes/index.ts:156 src/views/domain/ngx_conf/LogEntry.vue:86
 msgid "Error Logs"
 msgstr "Journaux d'erreurs"
 
@@ -567,7 +585,7 @@ msgstr "Journaux d'erreurs"
 msgid "Executable Path"
 msgstr "Chemin exécutable"
 
-#: src/views/certificate/Certificate.vue:84
+#: src/views/certificate/Certificate.vue:92
 msgid "Expired"
 msgstr ""
 
@@ -633,6 +651,11 @@ msgstr "Erreur de format %{msg}"
 msgid "Format successfully"
 msgstr "Formaté avec succès"
 
+#: src/views/certificate/Certificate.vue:45
+#, fuzzy
+msgid "General Certificate"
+msgstr "Changer de certificat"
+
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:60
 msgid "Generate"
 msgstr "Générer"
@@ -670,7 +693,7 @@ msgstr "GPT-4-32K"
 msgid "GPT-4-Turbo"
 msgstr "GPT-3.5-Turbo"
 
-#: src/routes/index.ts:44
+#: src/routes/index.ts:45
 msgid "Home"
 msgstr "Menu principal"
 
@@ -690,6 +713,10 @@ msgstr "Port HTTP"
 msgid "HTTP01"
 msgstr "HTTP01"
 
+#: src/constants/index.ts:19
+msgid "Info"
+msgstr ""
+
 #: src/language/constants.ts:27
 msgid "Initial core upgrader error"
 msgstr "Erreur du programme de mise à niveau initial du core"
@@ -698,7 +725,7 @@ msgstr "Erreur du programme de mise à niveau initial du core"
 msgid "Initialing core upgrader"
 msgstr "Initialisation du programme de mise à niveau du core"
 
-#: src/routes/index.ts:211 src/views/other/Install.vue:139
+#: src/routes/index.ts:220 src/views/other/Install.vue:139
 msgid "Install"
 msgstr "Installer"
 
@@ -760,12 +787,12 @@ msgstr "Localisation"
 msgid "Locations"
 msgstr "Localisations"
 
-#: src/views/certificate/CertificateEditor.vue:156
+#: src/views/certificate/CertificateEditor.vue:193
 #, fuzzy
 msgid "Log"
 msgstr "Connexion"
 
-#: src/routes/index.ts:217 src/views/other/Login.vue:147
+#: src/routes/index.ts:226 src/views/other/Login.vue:147
 msgid "Login"
 msgstr "Connexion"
 
@@ -773,7 +800,7 @@ msgstr "Connexion"
 msgid "Login successful"
 msgstr "Connexion réussie"
 
-#: src/layouts/HeaderLayout.vue:21
+#: src/layouts/HeaderLayout.vue:22
 msgid "Logout successful"
 msgstr "Déconnexion réussie"
 
@@ -786,18 +813,23 @@ msgstr ""
 "Assurez vous d'avoir configuré un reverse proxy pour le répertoire .well-"
 "known vers HTTPChallengePort avant d'obtenir le certificat."
 
-#: src/routes/index.ts:83
+#: src/routes/index.ts:84
 msgid "Manage Configs"
 msgstr "Gérer les configurations"
 
-#: src/routes/index.ts:58 src/views/domain/DomainList.vue:102
+#: src/routes/index.ts:59 src/views/domain/DomainList.vue:102
 msgid "Manage Sites"
 msgstr "Gérer les sites"
 
-#: src/routes/index.ts:176 src/views/user/User.vue:53
+#: src/routes/index.ts:185 src/views/user/User.vue:53
 msgid "Manage Users"
 msgstr "Gérer les utilisateurs"
 
+#: src/views/certificate/Certificate.vue:44
+#, fuzzy
+msgid "Managed Certificate"
+msgstr "Changer de certificat"
+
 #: src/views/dashboard/ServerAnalytic.vue:220
 msgid "Memory"
 msgstr "Mémoire"
@@ -807,12 +839,12 @@ msgid "Memory and Storage"
 msgstr "Mémoire et stockage"
 
 #: src/components/ChatGPT/ChatGPT.vue:256
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:336
 msgid "Modify"
 msgstr "Modifier"
 
-#: src/routes/index.ts:114 src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:115 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Modify Certificate"
 msgstr "État du certificat"
@@ -826,6 +858,7 @@ msgid "Multi-line Directive"
 msgstr "Directive multiligne"
 
 #: src/views/certificate/Certificate.vue:22
+#: src/views/certificate/CertificateEditor.vue:145
 #: src/views/certificate/DNSCredential.vue:13 src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:21
 #: src/views/domain/components/RightSettings.vue:81
@@ -880,7 +913,7 @@ msgstr "Contrôle Nginx"
 msgid "Nginx Error Log Path"
 msgstr "Chemin du journal des erreurs Nginx"
 
-#: src/routes/index.ts:145 src/views/nginx_log/NginxLog.vue:145
+#: src/routes/index.ts:146 src/views/nginx_log/NginxLog.vue:145
 msgid "Nginx Log"
 msgstr "Journal Nginx"
 
@@ -893,10 +926,12 @@ msgid "Nginx restarted successfully"
 msgstr "Nginx a redémarré avec succès"
 
 #: src/components/ChatGPT/ChatGPT.vue:277
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:345
-#: src/views/domain/DomainList.vue:143
+#: src/components/Notification/Notification.vue:84
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:351
+#: src/views/domain/DomainList.vue:142
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:81
 #: src/views/domain/ngx_conf/LocationEditor.vue:76
+#: src/views/notification/Notification.vue:71
 msgid "No"
 msgstr "Non"
 
@@ -905,11 +940,11 @@ msgstr "Non"
 msgid "Node Secret"
 msgstr "Secret Jwt"
 
-#: src/views/certificate/Certificate.vue:90
+#: src/views/certificate/Certificate.vue:98
 msgid "Not After"
 msgstr ""
 
-#: src/routes/index.ts:223 src/routes/index.ts:225
+#: src/routes/index.ts:232 src/routes/index.ts:234
 msgid "Not Found"
 msgstr "Introuvable"
 
@@ -922,6 +957,16 @@ msgstr "Non valide avant : %{date}"
 msgid "Note"
 msgstr "Note"
 
+#: src/views/notification/Notification.vue:63
+#, fuzzy
+msgid "Notification"
+msgstr "Certification"
+
+#: src/components/Notification/Notification.vue:82 src/routes/index.ts:177
+#, fuzzy
+msgid "Notifications"
+msgstr "Certification"
+
 #: src/views/domain/cert/components/ObtainCert.vue:191
 msgid "Obtain certificate"
 msgstr "Obtenir un certificat"
@@ -937,16 +982,18 @@ msgid "Offline"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:278
+#: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:59
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:140
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:346
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:144
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:352
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:104
 #: src/views/domain/cert/components/ObtainCert.vue:149
 #: src/views/domain/components/Deploy.vue:23
 #: src/views/domain/components/RightSettings.vue:51
-#: src/views/domain/DomainList.vue:144
+#: src/views/domain/DomainList.vue:143
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:215
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:47
+#: src/views/notification/Notification.vue:72
 msgid "OK"
 msgstr "OK"
 
@@ -1057,7 +1104,7 @@ msgstr ""
 msgid "Pre-release"
 msgstr ""
 
-#: src/routes/index.ts:184 src/views/preference/Preference.vue:85
+#: src/routes/index.ts:193 src/views/preference/Preference.vue:85
 msgid "Preference"
 msgstr "Préférence"
 
@@ -1123,12 +1170,27 @@ msgstr "Rechargement"
 msgid "Reloading nginx"
 msgstr "Rechargement de nginx"
 
+#: src/components/Notification/Notification.vue:52
+#, fuzzy
+msgid "Removed successfully"
+msgstr "Enregistré avec succès"
+
 #: src/views/certificate/RenewCert.vue:43
 #: src/views/certificate/RenewCert.vue:47
 #, fuzzy
 msgid "Renew Certificate"
 msgstr "Changer de certificat"
 
+#: src/language/constants.ts:38
+#, fuzzy
+msgid "Renew Certificate Error"
+msgstr "Changer de certificat"
+
+#: src/language/constants.ts:37
+#, fuzzy
+msgid "Renew Certificate Success"
+msgstr "Changer de certificat"
+
 #: src/views/certificate/RenewCert.vue:25
 #, fuzzy
 msgid "Renew successfully"
@@ -1159,7 +1221,7 @@ msgid "Running"
 msgstr "En cours d'éxécution"
 
 #: src/components/ChatGPT/ChatGPT.vue:259
-#: src/views/certificate/CertificateEditor.vue:175
+#: src/views/certificate/CertificateEditor.vue:212
 #: src/views/config/ConfigEdit.vue:98 src/views/domain/DomainEdit.vue:268
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:111
 #: src/views/preference/Preference.vue:113
@@ -1176,7 +1238,7 @@ msgid "Save error %{msg}"
 msgstr "Enregistrer l'erreur %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/views/certificate/CertificateEditor.vue:46
+#: src/views/certificate/CertificateEditor.vue:47
 #: src/views/preference/Preference.vue:58
 msgid "Save successfully"
 msgstr "Sauvegarde réussie"
@@ -1243,29 +1305,31 @@ msgstr "Utilisation du fournisseur de challenge HTTP01"
 msgid "Single Directive"
 msgstr "Directive unique"
 
-#: src/routes/index.ts:159
+#: src/routes/index.ts:160
 msgid "Site Logs"
 msgstr "Journaux du site"
 
-#: src/routes/index.ts:66
+#: src/routes/index.ts:67
 msgid "Sites List"
 msgstr "Liste des sites"
 
-#: src/views/certificate/CertificateEditor.vue:135
+#: src/views/certificate/CertificateEditor.vue:172
 #, fuzzy
 msgid "SSL Certificate Content"
 msgstr "Contenu de la certification SSL"
 
-#: src/views/certificate/CertificateEditor.vue:142
+#: src/views/certificate/CertificateEditor.vue:179
 #, fuzzy
 msgid "SSL Certificate Key Content"
 msgstr "Contenu de la clé de certification SSL"
 
-#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/Certificate.vue:71
+#: src/views/certificate/CertificateEditor.vue:163
 msgid "SSL Certificate Key Path"
 msgstr "Chemin de la clé du certificat SSL"
 
-#: src/views/certificate/Certificate.vue:55
+#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/CertificateEditor.vue:154
 msgid "SSL Certificate Path"
 msgstr "Chemin du certificat SSL"
 
@@ -1279,7 +1343,7 @@ msgstr "Connexion"
 msgid "Stable"
 msgstr "Tableau"
 
-#: src/views/certificate/Certificate.vue:71 src/views/domain/DomainList.vue:25
+#: src/views/certificate/Certificate.vue:79 src/views/domain/DomainList.vue:25
 #: src/views/environment/Environment.vue:78
 msgid "Status"
 msgstr "Statut"
@@ -1297,6 +1361,10 @@ msgstr "Stockage"
 msgid "Subject Name: %{subject}"
 msgstr "Nom du sujet : %{name}"
 
+#: src/constants/index.ts:20
+msgid "Success"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:233
 #, fuzzy
 msgid "Swap"
@@ -1310,7 +1378,7 @@ msgstr ""
 msgid "Switch to light theme"
 msgstr ""
 
-#: src/routes/index.ts:192
+#: src/routes/index.ts:201
 msgid "System"
 msgstr "Système"
 
@@ -1322,7 +1390,7 @@ msgstr "Tableau"
 msgid "Target"
 msgstr ""
 
-#: src/routes/index.ts:137 src/views/pty/Terminal.vue:95
+#: src/routes/index.ts:138 src/views/pty/Terminal.vue:95
 msgid "Terminal"
 msgstr "Terminal"
 
@@ -1357,14 +1425,14 @@ msgstr ""
 msgid "The username or password is incorrect"
 msgstr "Le pseudo ou mot de passe est incorect"
 
-#: src/views/certificate/CertificateEditor.vue:93
+#: src/views/certificate/CertificateEditor.vue:101
 #, fuzzy
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr ""
 "Cet élément de certification automatique n'est pas valide, veuillez le "
 "supprimer."
 
-#: src/views/certificate/CertificateEditor.vue:83
+#: src/views/certificate/CertificateEditor.vue:91
 msgid "This certificate is managed by Nginx UI"
 msgstr ""
 
@@ -1372,6 +1440,10 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr ""
 
+#: src/views/notification/Notification.vue:21
+msgid "Title"
+msgstr ""
+
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:42
 msgid ""
 "To make sure the certification auto-renewal can work normally, we need to "
@@ -1385,6 +1457,7 @@ msgstr ""
 "ce fichier et recharger le Nginx. Êtes-vous sûr de vouloir continuer?"
 
 #: src/views/certificate/Certificate.vue:39 src/views/config/config.ts:14
+#: src/views/notification/Notification.vue:15
 msgid "Type"
 msgstr "Type"
 
@@ -1400,7 +1473,7 @@ msgstr "Mis à jour le"
 msgid "Updated successfully"
 msgstr "Mis à jour avec succés"
 
-#: src/routes/index.ts:203 src/views/system/Upgrade.vue:143
+#: src/routes/index.ts:212 src/views/system/Upgrade.vue:143
 #: src/views/system/Upgrade.vue:235
 msgid "Upgrade"
 msgstr "Mettre à niveau"
@@ -1434,7 +1507,7 @@ msgstr "Nom d'utilisateur"
 msgid "Username (*)"
 msgstr "Nom d'utilisateur (*)"
 
-#: src/views/certificate/Certificate.vue:80
+#: src/views/certificate/Certificate.vue:88
 msgid "Valid"
 msgstr ""
 
@@ -1442,7 +1515,12 @@ msgstr ""
 msgid "View"
 msgstr "Voir"
 
-#: src/views/config/InspectConfig.vue:36
+#: src/components/Notification/Notification.vue:143
+#, fuzzy
+msgid "View all notifications"
+msgstr "Certification"
+
+#: src/constants/index.ts:18 src/views/config/InspectConfig.vue:36
 #: src/views/domain/cert/components/AutoCertStepOne.vue:23
 #: src/views/domain/DomainAdd.vue:114
 msgid "Warning"
@@ -1524,9 +1602,6 @@ msgstr "Vous pouvez vérifier la mise à niveau de Nginx UI sur cette page."
 #~ "supprimés. Veuillez noter que les configurations de temps ci-dessous sont "
 #~ "toutes en secondes."
 
-#~ msgid "Certification"
-#~ msgstr "Certification"
-
 #~ msgid "Delete ID: %{id}"
 #~ msgstr "Supprimer l'identifiant : %{id}"
 

+ 138 - 65
app/src/language/messages.pot

@@ -2,34 +2,35 @@ msgid ""
 msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 
-#: src/routes/index.ts:199
+#: src/routes/index.ts:208
 msgid "About"
 msgstr ""
 
-#: src/routes/index.ts:151
+#: src/routes/index.ts:152
 #: src/views/domain/ngx_conf/LogEntry.vue:78
 msgid "Access Logs"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:96
+#: src/views/certificate/Certificate.vue:104
 #: src/views/certificate/DNSCredential.vue:32
 #: src/views/config/config.ts:36
 #: src/views/domain/DomainList.vue:50
 #: src/views/environment/Environment.vue:105
+#: src/views/notification/Notification.vue:38
 #: src/views/user/User.vue:46
 msgid "Action"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:114
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:115
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
+#: src/views/certificate/Certificate.vue:120
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:115
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:308
 msgid "Add"
 msgstr ""
 
-#: src/routes/index.ts:122
-#: src/views/certificate/Certificate.vue:115
-#: src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:123
+#: src/views/certificate/CertificateEditor.vue:84
 msgid "Add Certificate"
 msgstr ""
 
@@ -42,7 +43,7 @@ msgstr ""
 msgid "Add Location"
 msgstr ""
 
-#: src/routes/index.ts:70
+#: src/routes/index.ts:71
 #: src/views/domain/DomainAdd.vue:91
 msgid "Add Site"
 msgstr ""
@@ -71,12 +72,17 @@ msgstr ""
 msgid "Arch"
 msgstr ""
 
+#: src/components/Notification/Notification.vue:86
+#: src/views/notification/Notification.vue:73
+msgid "Are you sure you want to clear all notifications?"
+msgstr ""
+
 #: src/components/ChatGPT/ChatGPT.vue:279
 msgid "Are you sure you want to clear the record of chat?"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:347
-#: src/views/domain/DomainList.vue:145
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:353
+#: src/views/domain/DomainList.vue:144
 msgid "Are you sure you want to delete?"
 msgstr ""
 
@@ -117,7 +123,7 @@ msgstr ""
 msgid "Auto-renewal enabled for %{name}"
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:168
+#: src/views/certificate/CertificateEditor.vue:205
 #: src/views/config/Config.vue:75
 #: src/views/config/ConfigEdit.vue:89
 #: src/views/domain/DomainEdit.vue:261
@@ -158,7 +164,7 @@ msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:260
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:58
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:139
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:143
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:103
 #: src/views/domain/cert/components/ObtainCert.vue:150
 #: src/views/domain/components/Deploy.vue:24
@@ -176,17 +182,17 @@ msgstr ""
 msgid "Certificate is valid"
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:119
+#: src/views/certificate/CertificateEditor.vue:127
 #: src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr ""
 
-#: src/routes/index.ts:100
-#: src/views/certificate/Certificate.vue:109
+#: src/routes/index.ts:101
+#: src/views/certificate/Certificate.vue:117
 msgid "Certificates"
 msgstr ""
 
-#: src/routes/index.ts:109
+#: src/routes/index.ts:110
 msgid "Certificates List"
 msgstr ""
 
@@ -220,9 +226,16 @@ msgid "Cleaning environment variables"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/Notification/Notification.vue:91
+#: src/views/notification/Notification.vue:78
 msgid "Clear"
 msgstr ""
 
+#: src/components/Notification/Notification.vue:42
+#: src/views/notification/Notification.vue:47
+msgid "Cleared successfully"
+msgstr ""
+
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/domain/ngx_conf/LocationEditor.vue:121
 #: src/views/domain/ngx_conf/LocationEditor.vue:90
@@ -276,6 +289,7 @@ msgstr ""
 msgid "Create Another"
 msgstr ""
 
+#: src/views/notification/Notification.vue:32
 #: src/views/user/User.vue:34
 msgid "Created at"
 msgstr ""
@@ -301,7 +315,7 @@ msgstr ""
 msgid "Custom"
 msgstr ""
 
-#: src/routes/index.ts:51
+#: src/routes/index.ts:52
 msgid "Dashboard"
 msgstr ""
 
@@ -309,7 +323,7 @@ msgstr ""
 msgid "Database (Optional, default: database)"
 msgstr ""
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:354
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:360
 #: src/views/domain/DomainList.vue:153
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:259
 msgid "Delete"
@@ -345,6 +359,10 @@ msgstr ""
 msgid "Description"
 msgstr ""
 
+#: src/views/notification/Notification.vue:28
+msgid "Details"
+msgstr ""
+
 #: src/views/system/About.vue:25
 msgid "Development Mode"
 msgstr ""
@@ -371,7 +389,7 @@ msgstr ""
 
 #: src/views/domain/cert/ChangeCert.vue:48
 #: src/views/domain/DomainEdit.vue:190
-#: src/views/domain/DomainList.vue:123
+#: src/views/domain/DomainList.vue:122
 msgid "Disabled"
 msgstr ""
 
@@ -384,7 +402,7 @@ msgstr ""
 msgid "Disk IO"
 msgstr ""
 
-#: src/routes/index.ts:130
+#: src/routes/index.ts:131
 #: src/views/certificate/DNSCredential.vue:39
 msgid "DNS Credentials"
 msgstr ""
@@ -428,7 +446,7 @@ msgstr ""
 msgid "Domain Config Created Successfully"
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:103
+#: src/views/certificate/CertificateEditor.vue:111
 msgid "Domains list is empty, try to reopen Auto Cert for %{config}"
 msgstr ""
 
@@ -445,7 +463,7 @@ msgid "Dry run mode enabled"
 msgstr ""
 
 #: src/views/domain/components/SiteDuplicate.vue:128
-#: src/views/domain/DomainList.vue:139
+#: src/views/domain/DomainList.vue:138
 msgid "Duplicate"
 msgstr ""
 
@@ -469,12 +487,12 @@ msgstr ""
 msgid "Edit %{n}"
 msgstr ""
 
-#: src/routes/index.ts:92
+#: src/routes/index.ts:93
 #: src/views/config/ConfigEdit.vue:83
 msgid "Edit Configuration"
 msgstr ""
 
-#: src/routes/index.ts:74
+#: src/routes/index.ts:75
 msgid "Edit Site"
 msgstr ""
 
@@ -514,7 +532,7 @@ msgstr ""
 #: src/views/domain/components/Deploy.vue:89
 #: src/views/domain/components/RightSettings.vue:75
 #: src/views/domain/DomainEdit.vue:184
-#: src/views/domain/DomainList.vue:131
+#: src/views/domain/DomainList.vue:130
 msgid "Enabled"
 msgstr ""
 
@@ -529,7 +547,7 @@ msgstr ""
 msgid "Encrypt website with Let's Encrypt"
 msgstr ""
 
-#: src/routes/index.ts:168
+#: src/routes/index.ts:169
 #: src/views/environment/Environment.vue:113
 msgid "Environment"
 msgstr ""
@@ -538,11 +556,12 @@ msgstr ""
 msgid "Environments"
 msgstr ""
 
+#: src/constants/index.ts:17
 #: src/views/config/InspectConfig.vue:47
 msgid "Error"
 msgstr ""
 
-#: src/routes/index.ts:155
+#: src/routes/index.ts:156
 #: src/views/domain/ngx_conf/LogEntry.vue:86
 msgid "Error Logs"
 msgstr ""
@@ -551,7 +570,7 @@ msgstr ""
 msgid "Executable Path"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:84
+#: src/views/certificate/Certificate.vue:92
 msgid "Expired"
 msgstr ""
 
@@ -615,6 +634,10 @@ msgstr ""
 msgid "Format successfully"
 msgstr ""
 
+#: src/views/certificate/Certificate.vue:45
+msgid "General Certificate"
+msgstr ""
+
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:60
 msgid "Generate"
 msgstr ""
@@ -651,7 +674,7 @@ msgstr ""
 msgid "GPT-4-Turbo"
 msgstr ""
 
-#: src/routes/index.ts:44
+#: src/routes/index.ts:45
 msgid "Home"
 msgstr ""
 
@@ -671,6 +694,10 @@ msgstr ""
 msgid "HTTP01"
 msgstr ""
 
+#: src/constants/index.ts:19
+msgid "Info"
+msgstr ""
+
 #: src/language/constants.ts:27
 msgid "Initial core upgrader error"
 msgstr ""
@@ -679,7 +706,7 @@ msgstr ""
 msgid "Initialing core upgrader"
 msgstr ""
 
-#: src/routes/index.ts:211
+#: src/routes/index.ts:220
 #: src/views/other/Install.vue:139
 msgid "Install"
 msgstr ""
@@ -737,11 +764,11 @@ msgstr ""
 msgid "Locations"
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:156
+#: src/views/certificate/CertificateEditor.vue:193
 msgid "Log"
 msgstr ""
 
-#: src/routes/index.ts:217
+#: src/routes/index.ts:226
 #: src/views/other/Login.vue:147
 msgid "Login"
 msgstr ""
@@ -751,7 +778,7 @@ msgstr ""
 msgid "Login successful"
 msgstr ""
 
-#: src/layouts/HeaderLayout.vue:21
+#: src/layouts/HeaderLayout.vue:22
 msgid "Logout successful"
 msgstr ""
 
@@ -759,20 +786,24 @@ msgstr ""
 msgid "Make sure you have configured a reverse proxy for .well-known directory to HTTPChallengePort before obtaining the certificate."
 msgstr ""
 
-#: src/routes/index.ts:83
+#: src/routes/index.ts:84
 msgid "Manage Configs"
 msgstr ""
 
-#: src/routes/index.ts:58
+#: src/routes/index.ts:59
 #: src/views/domain/DomainList.vue:102
 msgid "Manage Sites"
 msgstr ""
 
-#: src/routes/index.ts:176
+#: src/routes/index.ts:185
 #: src/views/user/User.vue:53
 msgid "Manage Users"
 msgstr ""
 
+#: src/views/certificate/Certificate.vue:44
+msgid "Managed Certificate"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:220
 msgid "Memory"
 msgstr ""
@@ -782,13 +813,13 @@ msgid "Memory and Storage"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:256
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:336
 msgid "Modify"
 msgstr ""
 
-#: src/routes/index.ts:114
-#: src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:115
+#: src/views/certificate/CertificateEditor.vue:84
 msgid "Modify Certificate"
 msgstr ""
 
@@ -801,6 +832,7 @@ msgid "Multi-line Directive"
 msgstr ""
 
 #: src/views/certificate/Certificate.vue:22
+#: src/views/certificate/CertificateEditor.vue:145
 #: src/views/certificate/DNSCredential.vue:13
 #: src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:21
@@ -856,7 +888,7 @@ msgstr ""
 msgid "Nginx Error Log Path"
 msgstr ""
 
-#: src/routes/index.ts:145
+#: src/routes/index.ts:146
 #: src/views/nginx_log/NginxLog.vue:145
 msgid "Nginx Log"
 msgstr ""
@@ -870,10 +902,12 @@ msgid "Nginx restarted successfully"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:277
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:345
-#: src/views/domain/DomainList.vue:143
+#: src/components/Notification/Notification.vue:84
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:351
+#: src/views/domain/DomainList.vue:142
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:81
 #: src/views/domain/ngx_conf/LocationEditor.vue:76
+#: src/views/notification/Notification.vue:71
 msgid "No"
 msgstr ""
 
@@ -881,12 +915,12 @@ msgstr ""
 msgid "Node Secret"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:90
+#: src/views/certificate/Certificate.vue:98
 msgid "Not After"
 msgstr ""
 
-#: src/routes/index.ts:223
-#: src/routes/index.ts:225
+#: src/routes/index.ts:232
+#: src/routes/index.ts:234
 msgid "Not Found"
 msgstr ""
 
@@ -899,6 +933,15 @@ msgstr ""
 msgid "Note"
 msgstr ""
 
+#: src/views/notification/Notification.vue:63
+msgid "Notification"
+msgstr ""
+
+#: src/components/Notification/Notification.vue:82
+#: src/routes/index.ts:177
+msgid "Notifications"
+msgstr ""
+
 #: src/views/domain/cert/components/ObtainCert.vue:191
 msgid "Obtain certificate"
 msgstr ""
@@ -914,16 +957,18 @@ msgid "Offline"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:278
+#: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:59
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:140
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:346
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:144
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:352
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:104
 #: src/views/domain/cert/components/ObtainCert.vue:149
 #: src/views/domain/components/Deploy.vue:23
 #: src/views/domain/components/RightSettings.vue:51
-#: src/views/domain/DomainList.vue:144
+#: src/views/domain/DomainList.vue:143
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:215
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:47
+#: src/views/notification/Notification.vue:72
 msgid "OK"
 msgstr ""
 
@@ -1024,7 +1069,7 @@ msgstr ""
 msgid "Pre-release"
 msgstr ""
 
-#: src/routes/index.ts:184
+#: src/routes/index.ts:193
 #: src/views/preference/Preference.vue:85
 msgid "Preference"
 msgstr ""
@@ -1088,11 +1133,23 @@ msgstr ""
 msgid "Reloading nginx"
 msgstr ""
 
+#: src/components/Notification/Notification.vue:52
+msgid "Removed successfully"
+msgstr ""
+
 #: src/views/certificate/RenewCert.vue:43
 #: src/views/certificate/RenewCert.vue:47
 msgid "Renew Certificate"
 msgstr ""
 
+#: src/language/constants.ts:38
+msgid "Renew Certificate Error"
+msgstr ""
+
+#: src/language/constants.ts:37
+msgid "Renew Certificate Success"
+msgstr ""
+
 #: src/views/certificate/RenewCert.vue:25
 msgid "Renew successfully"
 msgstr ""
@@ -1122,7 +1179,7 @@ msgid "Running"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:259
-#: src/views/certificate/CertificateEditor.vue:175
+#: src/views/certificate/CertificateEditor.vue:212
 #: src/views/config/ConfigEdit.vue:98
 #: src/views/domain/DomainEdit.vue:268
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:111
@@ -1141,7 +1198,7 @@ msgid "Save error %{msg}"
 msgstr ""
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/views/certificate/CertificateEditor.vue:46
+#: src/views/certificate/CertificateEditor.vue:47
 #: src/views/preference/Preference.vue:58
 msgid "Save successfully"
 msgstr ""
@@ -1209,27 +1266,29 @@ msgstr ""
 msgid "Single Directive"
 msgstr ""
 
-#: src/routes/index.ts:159
+#: src/routes/index.ts:160
 msgid "Site Logs"
 msgstr ""
 
-#: src/routes/index.ts:66
+#: src/routes/index.ts:67
 msgid "Sites List"
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:135
+#: src/views/certificate/CertificateEditor.vue:172
 msgid "SSL Certificate Content"
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:142
+#: src/views/certificate/CertificateEditor.vue:179
 msgid "SSL Certificate Key Content"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/Certificate.vue:71
+#: src/views/certificate/CertificateEditor.vue:163
 msgid "SSL Certificate Key Path"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:55
+#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/CertificateEditor.vue:154
 msgid "SSL Certificate Path"
 msgstr ""
 
@@ -1242,7 +1301,7 @@ msgstr ""
 msgid "Stable"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:71
+#: src/views/certificate/Certificate.vue:79
 #: src/views/domain/DomainList.vue:25
 #: src/views/environment/Environment.vue:78
 msgid "Status"
@@ -1260,6 +1319,10 @@ msgstr ""
 msgid "Subject Name: %{subject}"
 msgstr ""
 
+#: src/constants/index.ts:20
+msgid "Success"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:233
 msgid "Swap"
 msgstr ""
@@ -1272,7 +1335,7 @@ msgstr ""
 msgid "Switch to light theme"
 msgstr ""
 
-#: src/routes/index.ts:192
+#: src/routes/index.ts:201
 msgid "System"
 msgstr ""
 
@@ -1284,7 +1347,7 @@ msgstr ""
 msgid "Target"
 msgstr ""
 
-#: src/routes/index.ts:137
+#: src/routes/index.ts:138
 #: src/views/pty/Terminal.vue:95
 msgid "Terminal"
 msgstr ""
@@ -1309,11 +1372,11 @@ msgstr ""
 msgid "The username or password is incorrect"
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:93
+#: src/views/certificate/CertificateEditor.vue:101
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr ""
 
-#: src/views/certificate/CertificateEditor.vue:83
+#: src/views/certificate/CertificateEditor.vue:91
 msgid "This certificate is managed by Nginx UI"
 msgstr ""
 
@@ -1321,12 +1384,17 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr ""
 
+#: src/views/notification/Notification.vue:21
+msgid "Title"
+msgstr ""
+
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:42
 msgid "To make sure the certification auto-renewal can work normally, we need to add a location which can proxy the request from authority to backend, and we need to save this file and reload the Nginx. Are you sure you want to continue?"
 msgstr ""
 
 #: src/views/certificate/Certificate.vue:39
 #: src/views/config/config.ts:14
+#: src/views/notification/Notification.vue:15
 msgid "Type"
 msgstr ""
 
@@ -1344,7 +1412,7 @@ msgstr ""
 msgid "Updated successfully"
 msgstr ""
 
-#: src/routes/index.ts:203
+#: src/routes/index.ts:212
 #: src/views/system/Upgrade.vue:143
 #: src/views/system/Upgrade.vue:235
 msgid "Upgrade"
@@ -1379,7 +1447,7 @@ msgstr ""
 msgid "Username (*)"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:80
+#: src/views/certificate/Certificate.vue:88
 msgid "Valid"
 msgstr ""
 
@@ -1387,6 +1455,11 @@ msgstr ""
 msgid "View"
 msgstr ""
 
+#: src/components/Notification/Notification.vue:143
+msgid "View all notifications"
+msgstr ""
+
+#: src/constants/index.ts:18
 #: src/views/config/InspectConfig.vue:36
 #: src/views/domain/cert/components/AutoCertStepOne.vue:23
 #: src/views/domain/DomainAdd.vue:114

+ 143 - 69
app/src/language/ru_RU/app.po

@@ -9,30 +9,30 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
-#: src/routes/index.ts:199
+#: src/routes/index.ts:208
 msgid "About"
 msgstr "О проекте"
 
-#: src/routes/index.ts:151 src/views/domain/ngx_conf/LogEntry.vue:78
+#: src/routes/index.ts:152 src/views/domain/ngx_conf/LogEntry.vue:78
 msgid "Access Logs"
 msgstr "Журнал доступа"
 
-#: src/views/certificate/Certificate.vue:96
+#: src/views/certificate/Certificate.vue:104
 #: src/views/certificate/DNSCredential.vue:32 src/views/config/config.ts:36
 #: src/views/domain/DomainList.vue:50 src/views/environment/Environment.vue:105
-#: src/views/user/User.vue:46
+#: src/views/notification/Notification.vue:38 src/views/user/User.vue:46
 msgid "Action"
 msgstr "Действие"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:114
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:115
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
+#: src/views/certificate/Certificate.vue:120
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:115
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:308
 msgid "Add"
 msgstr "Добавить"
 
-#: src/routes/index.ts:122 src/views/certificate/Certificate.vue:115
-#: src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:123 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Add Certificate"
 msgstr "Статус сертификата"
@@ -46,7 +46,7 @@ msgstr "Добавить директиву ниже"
 msgid "Add Location"
 msgstr "Добавить Location"
 
-#: src/routes/index.ts:70 src/views/domain/DomainAdd.vue:91
+#: src/routes/index.ts:71 src/views/domain/DomainAdd.vue:91
 msgid "Add Site"
 msgstr "Добавть Сайт"
 
@@ -75,13 +75,19 @@ msgstr ""
 msgid "Arch"
 msgstr ""
 
+#: src/components/Notification/Notification.vue:86
+#: src/views/notification/Notification.vue:73
+#, fuzzy
+msgid "Are you sure you want to clear all notifications?"
+msgstr "Вы уверены, что хотите удалить эту директиву?"
+
 #: src/components/ChatGPT/ChatGPT.vue:279
 #, fuzzy
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "Вы уверены, что хотите удалить эту директиву?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:347
-#: src/views/domain/DomainList.vue:145
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:353
+#: src/views/domain/DomainList.vue:144
 #, fuzzy
 msgid "Are you sure you want to delete?"
 msgstr "Вы уверены, что хотите удалить эту директиву?"
@@ -124,7 +130,7 @@ msgstr "Автообновление отключено для %{name}"
 msgid "Auto-renewal enabled for %{name}"
 msgstr "Автообновление включено для %{name}"
 
-#: src/views/certificate/CertificateEditor.vue:168
+#: src/views/certificate/CertificateEditor.vue:205
 #: src/views/config/Config.vue:75 src/views/config/ConfigEdit.vue:89
 #: src/views/domain/DomainEdit.vue:261 src/views/nginx_log/NginxLog.vue:170
 msgid "Back"
@@ -166,7 +172,7 @@ msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:260
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:58
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:139
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:143
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:103
 #: src/views/domain/cert/components/ObtainCert.vue:150
 #: src/views/domain/components/Deploy.vue:24
@@ -184,17 +190,17 @@ msgstr "Срок действия сертификата истек"
 msgid "Certificate is valid"
 msgstr "Сертификат действителен"
 
-#: src/views/certificate/CertificateEditor.vue:119
+#: src/views/certificate/CertificateEditor.vue:127
 #: src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr "Статус сертификата"
 
-#: src/routes/index.ts:100 src/views/certificate/Certificate.vue:109
+#: src/routes/index.ts:101 src/views/certificate/Certificate.vue:117
 #, fuzzy
 msgid "Certificates"
 msgstr "Статус сертификата"
 
-#: src/routes/index.ts:109
+#: src/routes/index.ts:110
 #, fuzzy
 msgid "Certificates List"
 msgstr "Список"
@@ -230,9 +236,17 @@ msgid "Cleaning environment variables"
 msgstr "Очистка переменных среды"
 
 #: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/Notification/Notification.vue:91
+#: src/views/notification/Notification.vue:78
 msgid "Clear"
 msgstr "Очистить"
 
+#: src/components/Notification/Notification.vue:42
+#: src/views/notification/Notification.vue:47
+#, fuzzy
+msgid "Cleared successfully"
+msgstr "Отключено успешно"
+
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/domain/ngx_conf/LocationEditor.vue:121
 #: src/views/domain/ngx_conf/LocationEditor.vue:90
@@ -287,7 +301,7 @@ msgstr "CPU:"
 msgid "Create Another"
 msgstr "Создать еще"
 
-#: src/views/user/User.vue:34
+#: src/views/notification/Notification.vue:32 src/views/user/User.vue:34
 msgid "Created at"
 msgstr "Создан в"
 
@@ -312,7 +326,7 @@ msgstr "Текущяя версия"
 msgid "Custom"
 msgstr "Пользовательский"
 
-#: src/routes/index.ts:51
+#: src/routes/index.ts:52
 msgid "Dashboard"
 msgstr "Доска"
 
@@ -320,7 +334,7 @@ msgstr "Доска"
 msgid "Database (Optional, default: database)"
 msgstr "База данных (Опционально, по умолчанию: database)"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:354
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:360
 #: src/views/domain/DomainList.vue:153
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:259
 msgid "Delete"
@@ -358,6 +372,10 @@ msgstr "Saved successfully"
 msgid "Description"
 msgstr "Описание"
 
+#: src/views/notification/Notification.vue:28
+msgid "Details"
+msgstr ""
+
 #: src/views/system/About.vue:25
 msgid "Development Mode"
 msgstr "Режим разработки"
@@ -385,7 +403,7 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "Не удалось отключить автоматическое продление для %{name}"
 
 #: src/views/domain/cert/ChangeCert.vue:48 src/views/domain/DomainEdit.vue:190
-#: src/views/domain/DomainList.vue:123
+#: src/views/domain/DomainList.vue:122
 msgid "Disabled"
 msgstr "Отключено"
 
@@ -398,7 +416,7 @@ msgstr "Отключено успешно"
 msgid "Disk IO"
 msgstr "Нагрузка на Диск IO"
 
-#: src/routes/index.ts:130 src/views/certificate/DNSCredential.vue:39
+#: src/routes/index.ts:131 src/views/certificate/DNSCredential.vue:39
 msgid "DNS Credentials"
 msgstr ""
 
@@ -446,7 +464,7 @@ msgstr "Вы хотите удалить этот сервер?"
 msgid "Domain Config Created Successfully"
 msgstr "Конфигурация домена успешно создана"
 
-#: src/views/certificate/CertificateEditor.vue:103
+#: src/views/certificate/CertificateEditor.vue:111
 #, fuzzy
 msgid "Domains list is empty, try to reopen Auto Cert for %{config}"
 msgstr ""
@@ -465,7 +483,7 @@ msgid "Dry run mode enabled"
 msgstr "Включен пробный режим"
 
 #: src/views/domain/components/SiteDuplicate.vue:128
-#: src/views/domain/DomainList.vue:139
+#: src/views/domain/DomainList.vue:138
 msgid "Duplicate"
 msgstr "Дублировать"
 
@@ -493,11 +511,11 @@ msgstr "Saved successfully"
 msgid "Edit %{n}"
 msgstr "Редактировать %{n}"
 
-#: src/routes/index.ts:92 src/views/config/ConfigEdit.vue:83
+#: src/routes/index.ts:93 src/views/config/ConfigEdit.vue:83
 msgid "Edit Configuration"
 msgstr "Редактировать Конфигурацию"
 
-#: src/routes/index.ts:74
+#: src/routes/index.ts:75
 msgid "Edit Site"
 msgstr "Редактировать Сайт"
 
@@ -538,7 +556,7 @@ msgstr "Включить TLS"
 #: src/views/domain/cert/ChangeCert.vue:44
 #: src/views/domain/components/Deploy.vue:89
 #: src/views/domain/components/RightSettings.vue:75
-#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:131
+#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:130
 msgid "Enabled"
 msgstr "Включено"
 
@@ -552,7 +570,7 @@ msgstr "Активировано успешно"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "Использовать для сайта Let's Encrypt"
 
-#: src/routes/index.ts:168 src/views/environment/Environment.vue:113
+#: src/routes/index.ts:169 src/views/environment/Environment.vue:113
 msgid "Environment"
 msgstr "Окружение"
 
@@ -561,11 +579,11 @@ msgstr "Окружение"
 msgid "Environments"
 msgstr "Комментарии"
 
-#: src/views/config/InspectConfig.vue:47
+#: src/constants/index.ts:17 src/views/config/InspectConfig.vue:47
 msgid "Error"
 msgstr "Ошибка"
 
-#: src/routes/index.ts:155 src/views/domain/ngx_conf/LogEntry.vue:86
+#: src/routes/index.ts:156 src/views/domain/ngx_conf/LogEntry.vue:86
 msgid "Error Logs"
 msgstr "Ошибка логирования"
 
@@ -573,7 +591,7 @@ msgstr "Ошибка логирования"
 msgid "Executable Path"
 msgstr "Исполняемый путь"
 
-#: src/views/certificate/Certificate.vue:84
+#: src/views/certificate/Certificate.vue:92
 msgid "Expired"
 msgstr ""
 
@@ -639,6 +657,11 @@ msgstr "Ошибка форматирования %{msg}"
 msgid "Format successfully"
 msgstr "Форматирование успешно"
 
+#: src/views/certificate/Certificate.vue:45
+#, fuzzy
+msgid "General Certificate"
+msgstr "Сертификат действителен"
+
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:60
 msgid "Generate"
 msgstr "Сгенерировать"
@@ -676,7 +699,7 @@ msgstr ""
 msgid "GPT-4-Turbo"
 msgstr ""
 
-#: src/routes/index.ts:44
+#: src/routes/index.ts:45
 msgid "Home"
 msgstr "Главная"
 
@@ -696,6 +719,10 @@ msgstr "Порт HTTP"
 msgid "HTTP01"
 msgstr ""
 
+#: src/constants/index.ts:19
+msgid "Info"
+msgstr ""
+
 #: src/language/constants.ts:27
 msgid "Initial core upgrader error"
 msgstr "Ошибка первоначального обновления ядра"
@@ -704,7 +731,7 @@ msgstr "Ошибка первоначального обновления ядр
 msgid "Initialing core upgrader"
 msgstr "Инициализация программы обновления ядра"
 
-#: src/routes/index.ts:211 src/views/other/Install.vue:139
+#: src/routes/index.ts:220 src/views/other/Install.vue:139
 msgid "Install"
 msgstr "Установить"
 
@@ -766,12 +793,12 @@ msgstr "Location"
 msgid "Locations"
 msgstr "Locations"
 
-#: src/views/certificate/CertificateEditor.vue:156
+#: src/views/certificate/CertificateEditor.vue:193
 #, fuzzy
 msgid "Log"
 msgstr "Логин"
 
-#: src/routes/index.ts:217 src/views/other/Login.vue:147
+#: src/routes/index.ts:226 src/views/other/Login.vue:147
 msgid "Login"
 msgstr "Логин"
 
@@ -779,7 +806,7 @@ msgstr "Логин"
 msgid "Login successful"
 msgstr "Авторизация успешна"
 
-#: src/layouts/HeaderLayout.vue:21
+#: src/layouts/HeaderLayout.vue:22
 msgid "Logout successful"
 msgstr "Выход выполнен успешно"
 
@@ -792,18 +819,23 @@ msgstr ""
 "Убедитесь, что вы настроили обратный прокси-сервер для каталога .well-known "
 "на HTTPChallengePort перед получением сертификата»."
 
-#: src/routes/index.ts:83
+#: src/routes/index.ts:84
 msgid "Manage Configs"
 msgstr "Конфигурации"
 
-#: src/routes/index.ts:58 src/views/domain/DomainList.vue:102
+#: src/routes/index.ts:59 src/views/domain/DomainList.vue:102
 msgid "Manage Sites"
 msgstr "Сайты"
 
-#: src/routes/index.ts:176 src/views/user/User.vue:53
+#: src/routes/index.ts:185 src/views/user/User.vue:53
 msgid "Manage Users"
 msgstr "Пользователи"
 
+#: src/views/certificate/Certificate.vue:44
+#, fuzzy
+msgid "Managed Certificate"
+msgstr "Сертификат действителен"
+
 #: src/views/dashboard/ServerAnalytic.vue:220
 msgid "Memory"
 msgstr "Память"
@@ -813,13 +845,13 @@ msgid "Memory and Storage"
 msgstr "Память и хранилище"
 
 #: src/components/ChatGPT/ChatGPT.vue:256
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:336
 #, fuzzy
 msgid "Modify"
 msgstr "Изменить"
 
-#: src/routes/index.ts:114 src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:115 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Modify Certificate"
 msgstr "Статус сертификата"
@@ -834,6 +866,7 @@ msgid "Multi-line Directive"
 msgstr "Одиночная директива"
 
 #: src/views/certificate/Certificate.vue:22
+#: src/views/certificate/CertificateEditor.vue:145
 #: src/views/certificate/DNSCredential.vue:13 src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:21
 #: src/views/domain/components/RightSettings.vue:81
@@ -889,7 +922,7 @@ msgstr "Управление Nginx"
 msgid "Nginx Error Log Path"
 msgstr "Путь для Nginx Error Log"
 
-#: src/routes/index.ts:145 src/views/nginx_log/NginxLog.vue:145
+#: src/routes/index.ts:146 src/views/nginx_log/NginxLog.vue:145
 msgid "Nginx Log"
 msgstr "Журнал"
 
@@ -904,10 +937,12 @@ msgid "Nginx restarted successfully"
 msgstr "Nginx успешно перезапущен"
 
 #: src/components/ChatGPT/ChatGPT.vue:277
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:345
-#: src/views/domain/DomainList.vue:143
+#: src/components/Notification/Notification.vue:84
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:351
+#: src/views/domain/DomainList.vue:142
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:81
 #: src/views/domain/ngx_conf/LocationEditor.vue:76
+#: src/views/notification/Notification.vue:71
 msgid "No"
 msgstr "Нет"
 
@@ -915,11 +950,11 @@ msgstr "Нет"
 msgid "Node Secret"
 msgstr ""
 
-#: src/views/certificate/Certificate.vue:90
+#: src/views/certificate/Certificate.vue:98
 msgid "Not After"
 msgstr ""
 
-#: src/routes/index.ts:223 src/routes/index.ts:225
+#: src/routes/index.ts:232 src/routes/index.ts:234
 msgid "Not Found"
 msgstr "Не найден"
 
@@ -932,6 +967,16 @@ msgstr "Недействительно до: %{date}"
 msgid "Note"
 msgstr "Заметка"
 
+#: src/views/notification/Notification.vue:63
+#, fuzzy
+msgid "Notification"
+msgstr "Сертификат"
+
+#: src/components/Notification/Notification.vue:82 src/routes/index.ts:177
+#, fuzzy
+msgid "Notifications"
+msgstr "Сертификат"
+
 #: src/views/domain/cert/components/ObtainCert.vue:191
 #, fuzzy
 msgid "Obtain certificate"
@@ -948,16 +993,18 @@ msgid "Offline"
 msgstr ""
 
 #: src/components/ChatGPT/ChatGPT.vue:278
+#: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:59
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:140
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:346
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:144
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:352
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:104
 #: src/views/domain/cert/components/ObtainCert.vue:149
 #: src/views/domain/components/Deploy.vue:23
 #: src/views/domain/components/RightSettings.vue:51
-#: src/views/domain/DomainList.vue:144
+#: src/views/domain/DomainList.vue:143
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:215
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:47
+#: src/views/notification/Notification.vue:72
 msgid "OK"
 msgstr ""
 
@@ -1064,7 +1111,7 @@ msgstr ""
 msgid "Pre-release"
 msgstr ""
 
-#: src/routes/index.ts:184 src/views/preference/Preference.vue:85
+#: src/routes/index.ts:193 src/views/preference/Preference.vue:85
 msgid "Preference"
 msgstr "Настройки"
 
@@ -1129,12 +1176,27 @@ msgstr "Перезагружается"
 msgid "Reloading nginx"
 msgstr "Перезагружается nginx"
 
+#: src/components/Notification/Notification.vue:52
+#, fuzzy
+msgid "Removed successfully"
+msgstr "Успешно сохранено"
+
 #: src/views/certificate/RenewCert.vue:43
 #: src/views/certificate/RenewCert.vue:47
 #, fuzzy
 msgid "Renew Certificate"
 msgstr "Сертификат действителен"
 
+#: src/language/constants.ts:38
+#, fuzzy
+msgid "Renew Certificate Error"
+msgstr "Сертификат действителен"
+
+#: src/language/constants.ts:37
+#, fuzzy
+msgid "Renew Certificate Success"
+msgstr "Сертификат действителен"
+
 #: src/views/certificate/RenewCert.vue:25
 #, fuzzy
 msgid "Renew successfully"
@@ -1166,7 +1228,7 @@ msgid "Running"
 msgstr "Выполняется"
 
 #: src/components/ChatGPT/ChatGPT.vue:259
-#: src/views/certificate/CertificateEditor.vue:175
+#: src/views/certificate/CertificateEditor.vue:212
 #: src/views/config/ConfigEdit.vue:98 src/views/domain/DomainEdit.vue:268
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:111
 #: src/views/preference/Preference.vue:113
@@ -1183,7 +1245,7 @@ msgid "Save error %{msg}"
 msgstr "Ошибка сохранения %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/views/certificate/CertificateEditor.vue:46
+#: src/views/certificate/CertificateEditor.vue:47
 #: src/views/preference/Preference.vue:58
 #, fuzzy
 msgid "Save successfully"
@@ -1250,31 +1312,33 @@ msgstr ""
 msgid "Single Directive"
 msgstr "Одиночная Директива"
 
-#: src/routes/index.ts:159
+#: src/routes/index.ts:160
 #, fuzzy
 msgid "Site Logs"
 msgstr "Логи сайтов"
 
-#: src/routes/index.ts:66
+#: src/routes/index.ts:67
 msgid "Sites List"
 msgstr "Список сайтов"
 
-#: src/views/certificate/CertificateEditor.vue:135
+#: src/views/certificate/CertificateEditor.vue:172
 #, fuzzy
 msgid "SSL Certificate Content"
 msgstr "Содержание сертификата SSL"
 
-#: src/views/certificate/CertificateEditor.vue:142
+#: src/views/certificate/CertificateEditor.vue:179
 #, fuzzy
 msgid "SSL Certificate Key Content"
 msgstr "Содержание ключа сертификата SSL"
 
-#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/Certificate.vue:71
+#: src/views/certificate/CertificateEditor.vue:163
 #, fuzzy
 msgid "SSL Certificate Key Path"
 msgstr "Путь к ключу сертификата SSL"
 
-#: src/views/certificate/Certificate.vue:55
+#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/CertificateEditor.vue:154
 #, fuzzy
 msgid "SSL Certificate Path"
 msgstr "Путь к сертификату SSL"
@@ -1289,7 +1353,7 @@ msgstr "Логин"
 msgid "Stable"
 msgstr "Таблица"
 
-#: src/views/certificate/Certificate.vue:71 src/views/domain/DomainList.vue:25
+#: src/views/certificate/Certificate.vue:79 src/views/domain/DomainList.vue:25
 #: src/views/environment/Environment.vue:78
 msgid "Status"
 msgstr "Статус"
@@ -1307,6 +1371,10 @@ msgstr "Хранилище"
 msgid "Subject Name: %{subject}"
 msgstr "Название темы: %{name}"
 
+#: src/constants/index.ts:20
+msgid "Success"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:233
 msgid "Swap"
 msgstr "Своп"
@@ -1319,7 +1387,7 @@ msgstr ""
 msgid "Switch to light theme"
 msgstr ""
 
-#: src/routes/index.ts:192
+#: src/routes/index.ts:201
 msgid "System"
 msgstr "Система"
 
@@ -1332,7 +1400,7 @@ msgstr "Таблица"
 msgid "Target"
 msgstr ""
 
-#: src/routes/index.ts:137 src/views/pty/Terminal.vue:95
+#: src/routes/index.ts:138 src/views/pty/Terminal.vue:95
 msgid "Terminal"
 msgstr "Терминал"
 
@@ -1366,12 +1434,12 @@ msgstr ""
 msgid "The username or password is incorrect"
 msgstr "Имя пользователя или пароль неверны"
 
-#: src/views/certificate/CertificateEditor.vue:93
+#: src/views/certificate/CertificateEditor.vue:101
 #, fuzzy
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "Этот элемент автосертификата недействителен, удалите его.."
 
-#: src/views/certificate/CertificateEditor.vue:83
+#: src/views/certificate/CertificateEditor.vue:91
 msgid "This certificate is managed by Nginx UI"
 msgstr ""
 
@@ -1379,6 +1447,10 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr ""
 
+#: src/views/notification/Notification.vue:21
+msgid "Title"
+msgstr ""
+
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:42
 msgid ""
 "To make sure the certification auto-renewal can work normally, we need to "
@@ -1388,6 +1460,7 @@ msgid ""
 msgstr ""
 
 #: src/views/certificate/Certificate.vue:39 src/views/config/config.ts:14
+#: src/views/notification/Notification.vue:15
 msgid "Type"
 msgstr "Тип"
 
@@ -1404,7 +1477,7 @@ msgstr "Обновлено в"
 msgid "Updated successfully"
 msgstr "Обновлено успешно"
 
-#: src/routes/index.ts:203 src/views/system/Upgrade.vue:143
+#: src/routes/index.ts:212 src/views/system/Upgrade.vue:143
 #: src/views/system/Upgrade.vue:235
 msgid "Upgrade"
 msgstr "Обновление"
@@ -1439,7 +1512,7 @@ msgstr "Имя пользователя"
 msgid "Username (*)"
 msgstr "Имя пользователя (*)"
 
-#: src/views/certificate/Certificate.vue:80
+#: src/views/certificate/Certificate.vue:88
 msgid "Valid"
 msgstr ""
 
@@ -1447,7 +1520,12 @@ msgstr ""
 msgid "View"
 msgstr "Просмотр"
 
-#: src/views/config/InspectConfig.vue:36
+#: src/components/Notification/Notification.vue:143
+#, fuzzy
+msgid "View all notifications"
+msgstr "Сертификат"
+
+#: src/constants/index.ts:18 src/views/config/InspectConfig.vue:36
 #: src/views/domain/cert/components/AutoCertStepOne.vue:23
 #: src/views/domain/DomainAdd.vue:114
 msgid "Warning"
@@ -1510,10 +1588,6 @@ msgstr "Вы можете проверить обновление Nginx UI на
 #~ msgid "Auto cert is enabled, please do not modify this certification."
 #~ msgstr "Включено автомотическое получения сертификата. Не правте руками."
 
-#, fuzzy
-#~ msgid "Certification"
-#~ msgstr "Сертификат"
-
 #~ msgid "Delete ID: %{id}"
 #~ msgstr "Удалить ID: %{id}"
 

BIN
app/src/language/zh_CN/app.mo


+ 133 - 68
app/src/language/zh_CN/app.po

@@ -13,30 +13,30 @@ msgstr ""
 "Generated-By: easygettext\n"
 "X-Generator: Poedit 3.4.1\n"
 
-#: src/routes/index.ts:199
+#: src/routes/index.ts:208
 msgid "About"
 msgstr "关于"
 
-#: src/routes/index.ts:151 src/views/domain/ngx_conf/LogEntry.vue:78
+#: src/routes/index.ts:152 src/views/domain/ngx_conf/LogEntry.vue:78
 msgid "Access Logs"
 msgstr "访问日志"
 
-#: src/views/certificate/Certificate.vue:96
+#: src/views/certificate/Certificate.vue:104
 #: src/views/certificate/DNSCredential.vue:32 src/views/config/config.ts:36
 #: src/views/domain/DomainList.vue:50 src/views/environment/Environment.vue:105
-#: src/views/user/User.vue:46
+#: src/views/notification/Notification.vue:38 src/views/user/User.vue:46
 msgid "Action"
 msgstr "操作"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:114
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:115
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
+#: src/views/certificate/Certificate.vue:120
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:115
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:308
 msgid "Add"
 msgstr "添加"
 
-#: src/routes/index.ts:122 src/views/certificate/Certificate.vue:115
-#: src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:123 src/views/certificate/CertificateEditor.vue:84
 msgid "Add Certificate"
 msgstr "添加证书"
 
@@ -49,7 +49,7 @@ msgstr "在下面添加指令"
 msgid "Add Location"
 msgstr "添加 Location"
 
-#: src/routes/index.ts:70 src/views/domain/DomainAdd.vue:91
+#: src/routes/index.ts:71 src/views/domain/DomainAdd.vue:91
 msgid "Add Site"
 msgstr "添加站点"
 
@@ -77,12 +77,17 @@ msgstr "API Token"
 msgid "Arch"
 msgstr "架构"
 
+#: src/components/Notification/Notification.vue:86
+#: src/views/notification/Notification.vue:73
+msgid "Are you sure you want to clear all notifications?"
+msgstr "您确定要清除所有通知吗?"
+
 #: src/components/ChatGPT/ChatGPT.vue:279
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "你确定你要清除聊天记录吗?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:347
-#: src/views/domain/DomainList.vue:145
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:353
+#: src/views/domain/DomainList.vue:144
 msgid "Are you sure you want to delete?"
 msgstr "您确定要删除吗?"
 
@@ -123,7 +128,7 @@ msgstr "成功关闭 %{name} 自动续签"
 msgid "Auto-renewal enabled for %{name}"
 msgstr "成功启用 %{name} 自动续签"
 
-#: src/views/certificate/CertificateEditor.vue:168
+#: src/views/certificate/CertificateEditor.vue:205
 #: src/views/config/Config.vue:75 src/views/config/ConfigEdit.vue:89
 #: src/views/domain/DomainEdit.vue:261 src/views/nginx_log/NginxLog.vue:170
 msgid "Back"
@@ -162,7 +167,7 @@ msgstr "CADir"
 
 #: src/components/ChatGPT/ChatGPT.vue:260
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:58
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:139
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:143
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:103
 #: src/views/domain/cert/components/ObtainCert.vue:150
 #: src/views/domain/components/Deploy.vue:24
@@ -180,16 +185,16 @@ msgstr "此证书已过期"
 msgid "Certificate is valid"
 msgstr "此证书有效"
 
-#: src/views/certificate/CertificateEditor.vue:119
+#: src/views/certificate/CertificateEditor.vue:127
 #: src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr "证书状态"
 
-#: src/routes/index.ts:100 src/views/certificate/Certificate.vue:109
+#: src/routes/index.ts:101 src/views/certificate/Certificate.vue:117
 msgid "Certificates"
 msgstr "证书"
 
-#: src/routes/index.ts:109
+#: src/routes/index.ts:110
 msgid "Certificates List"
 msgstr "证书列表"
 
@@ -223,9 +228,16 @@ msgid "Cleaning environment variables"
 msgstr "正在清理环境变量"
 
 #: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/Notification/Notification.vue:91
+#: src/views/notification/Notification.vue:78
 msgid "Clear"
 msgstr "清空"
 
+#: src/components/Notification/Notification.vue:42
+#: src/views/notification/Notification.vue:47
+msgid "Cleared successfully"
+msgstr "清除成功"
+
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/domain/ngx_conf/LocationEditor.vue:121
 #: src/views/domain/ngx_conf/LocationEditor.vue:90
@@ -279,7 +291,7 @@ msgstr "CPU:"
 msgid "Create Another"
 msgstr "再创建一个"
 
-#: src/views/user/User.vue:34
+#: src/views/notification/Notification.vue:32 src/views/user/User.vue:34
 msgid "Created at"
 msgstr "创建时间"
 
@@ -304,7 +316,7 @@ msgstr "当前版本"
 msgid "Custom"
 msgstr "自定义"
 
-#: src/routes/index.ts:51
+#: src/routes/index.ts:52
 msgid "Dashboard"
 msgstr "仪表盘"
 
@@ -312,7 +324,7 @@ msgstr "仪表盘"
 msgid "Database (Optional, default: database)"
 msgstr "数据库 (可选,默认: database)"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:354
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:360
 #: src/views/domain/DomainList.vue:153
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:259
 msgid "Delete"
@@ -348,6 +360,10 @@ msgstr "部署成功"
 msgid "Description"
 msgstr "描述"
 
+#: src/views/notification/Notification.vue:28
+msgid "Details"
+msgstr "详情"
+
 #: src/views/system/About.vue:25
 msgid "Development Mode"
 msgstr "开发模式"
@@ -373,7 +389,7 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "关闭 %{name} 自动续签失败"
 
 #: src/views/domain/cert/ChangeCert.vue:48 src/views/domain/DomainEdit.vue:190
-#: src/views/domain/DomainList.vue:123
+#: src/views/domain/DomainList.vue:122
 msgid "Disabled"
 msgstr "禁用"
 
@@ -386,7 +402,7 @@ msgstr "禁用成功"
 msgid "Disk IO"
 msgstr "磁盘 IO"
 
-#: src/routes/index.ts:130 src/views/certificate/DNSCredential.vue:39
+#: src/routes/index.ts:131 src/views/certificate/DNSCredential.vue:39
 msgid "DNS Credentials"
 msgstr "DNS 凭证"
 
@@ -428,7 +444,7 @@ msgstr "你想删除这个服务器吗?"
 msgid "Domain Config Created Successfully"
 msgstr "域名配置文件创建成功"
 
-#: src/views/certificate/CertificateEditor.vue:103
+#: src/views/certificate/CertificateEditor.vue:111
 msgid "Domains list is empty, try to reopen Auto Cert for %{config}"
 msgstr "域名列表为空,请尝试为 %{config} 重新打开证书自动续期。"
 
@@ -445,7 +461,7 @@ msgid "Dry run mode enabled"
 msgstr "试运行模式已启动"
 
 #: src/views/domain/components/SiteDuplicate.vue:128
-#: src/views/domain/DomainList.vue:139
+#: src/views/domain/DomainList.vue:138
 msgid "Duplicate"
 msgstr "复制"
 
@@ -469,11 +485,11 @@ msgstr "成功复制到本地"
 msgid "Edit %{n}"
 msgstr "编辑 %{n}"
 
-#: src/routes/index.ts:92 src/views/config/ConfigEdit.vue:83
+#: src/routes/index.ts:93 src/views/config/ConfigEdit.vue:83
 msgid "Edit Configuration"
 msgstr "编辑配置"
 
-#: src/routes/index.ts:74
+#: src/routes/index.ts:75
 msgid "Edit Site"
 msgstr "编辑站点"
 
@@ -512,7 +528,7 @@ msgstr "启用 TLS"
 #: src/views/domain/cert/ChangeCert.vue:44
 #: src/views/domain/components/Deploy.vue:89
 #: src/views/domain/components/RightSettings.vue:75
-#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:131
+#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:130
 msgid "Enabled"
 msgstr "启用"
 
@@ -526,7 +542,7 @@ msgstr "启用成功"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "用 Let's Encrypt 对网站进行加密"
 
-#: src/routes/index.ts:168 src/views/environment/Environment.vue:113
+#: src/routes/index.ts:169 src/views/environment/Environment.vue:113
 msgid "Environment"
 msgstr "环境"
 
@@ -534,11 +550,11 @@ msgstr "环境"
 msgid "Environments"
 msgstr "环境"
 
-#: src/views/config/InspectConfig.vue:47
+#: src/constants/index.ts:17 src/views/config/InspectConfig.vue:47
 msgid "Error"
 msgstr "错误"
 
-#: src/routes/index.ts:155 src/views/domain/ngx_conf/LogEntry.vue:86
+#: src/routes/index.ts:156 src/views/domain/ngx_conf/LogEntry.vue:86
 msgid "Error Logs"
 msgstr "错误日志"
 
@@ -546,7 +562,7 @@ msgstr "错误日志"
 msgid "Executable Path"
 msgstr "可执行文件路径"
 
-#: src/views/certificate/Certificate.vue:84
+#: src/views/certificate/Certificate.vue:92
 msgid "Expired"
 msgstr "已过期"
 
@@ -609,6 +625,10 @@ msgstr "保存错误 %{msg}"
 msgid "Format successfully"
 msgstr "格式化成功"
 
+#: src/views/certificate/Certificate.vue:45
+msgid "General Certificate"
+msgstr "普通证书"
+
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:60
 msgid "Generate"
 msgstr "生成"
@@ -645,7 +665,7 @@ msgstr "GPT-4-32K"
 msgid "GPT-4-Turbo"
 msgstr "GPT-4-Turbo"
 
-#: src/routes/index.ts:44
+#: src/routes/index.ts:45
 msgid "Home"
 msgstr "首页"
 
@@ -665,6 +685,10 @@ msgstr "HTTP 监听端口"
 msgid "HTTP01"
 msgstr "HTTP01"
 
+#: src/constants/index.ts:19
+msgid "Info"
+msgstr "信息"
+
 #: src/language/constants.ts:27
 msgid "Initial core upgrader error"
 msgstr "初始化核心升级程序错误"
@@ -673,7 +697,7 @@ msgstr "初始化核心升级程序错误"
 msgid "Initialing core upgrader"
 msgstr "初始化核心升级器"
 
-#: src/routes/index.ts:211 src/views/other/Install.vue:139
+#: src/routes/index.ts:220 src/views/other/Install.vue:139
 msgid "Install"
 msgstr "安装"
 
@@ -730,11 +754,11 @@ msgstr "Location"
 msgid "Locations"
 msgstr "Locations"
 
-#: src/views/certificate/CertificateEditor.vue:156
+#: src/views/certificate/CertificateEditor.vue:193
 msgid "Log"
 msgstr "日志"
 
-#: src/routes/index.ts:217 src/views/other/Login.vue:147
+#: src/routes/index.ts:226 src/views/other/Login.vue:147
 msgid "Login"
 msgstr "登录"
 
@@ -742,7 +766,7 @@ msgstr "登录"
 msgid "Login successful"
 msgstr "登录成功"
 
-#: src/layouts/HeaderLayout.vue:21
+#: src/layouts/HeaderLayout.vue:22
 msgid "Logout successful"
 msgstr "登出成功"
 
@@ -754,18 +778,22 @@ msgstr ""
 "在获取签发证书前,请确保配置文件中已将 .well-known 目录反向代理到 "
 "HTTPChallengePort。"
 
-#: src/routes/index.ts:83
+#: src/routes/index.ts:84
 msgid "Manage Configs"
 msgstr "配置管理"
 
-#: src/routes/index.ts:58 src/views/domain/DomainList.vue:102
+#: src/routes/index.ts:59 src/views/domain/DomainList.vue:102
 msgid "Manage Sites"
 msgstr "网站管理"
 
-#: src/routes/index.ts:176 src/views/user/User.vue:53
+#: src/routes/index.ts:185 src/views/user/User.vue:53
 msgid "Manage Users"
 msgstr "用户管理"
 
+#: src/views/certificate/Certificate.vue:44
+msgid "Managed Certificate"
+msgstr "托管证书"
+
 #: src/views/dashboard/ServerAnalytic.vue:220
 msgid "Memory"
 msgstr "内存"
@@ -775,12 +803,12 @@ msgid "Memory and Storage"
 msgstr "内存与存储"
 
 #: src/components/ChatGPT/ChatGPT.vue:256
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:336
 msgid "Modify"
 msgstr "修改"
 
-#: src/routes/index.ts:114 src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:115 src/views/certificate/CertificateEditor.vue:84
 msgid "Modify Certificate"
 msgstr "修改证书"
 
@@ -793,6 +821,7 @@ msgid "Multi-line Directive"
 msgstr "多行指令"
 
 #: src/views/certificate/Certificate.vue:22
+#: src/views/certificate/CertificateEditor.vue:145
 #: src/views/certificate/DNSCredential.vue:13 src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:21
 #: src/views/domain/components/RightSettings.vue:81
@@ -846,7 +875,7 @@ msgstr "控制 Nginx"
 msgid "Nginx Error Log Path"
 msgstr "Nginx 错误日志路径"
 
-#: src/routes/index.ts:145 src/views/nginx_log/NginxLog.vue:145
+#: src/routes/index.ts:146 src/views/nginx_log/NginxLog.vue:145
 msgid "Nginx Log"
 msgstr "Nginx 日志"
 
@@ -859,10 +888,12 @@ msgid "Nginx restarted successfully"
 msgstr "Nginx 重启成功"
 
 #: src/components/ChatGPT/ChatGPT.vue:277
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:345
-#: src/views/domain/DomainList.vue:143
+#: src/components/Notification/Notification.vue:84
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:351
+#: src/views/domain/DomainList.vue:142
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:81
 #: src/views/domain/ngx_conf/LocationEditor.vue:76
+#: src/views/notification/Notification.vue:71
 msgid "No"
 msgstr "取消"
 
@@ -870,11 +901,11 @@ msgstr "取消"
 msgid "Node Secret"
 msgstr "节点密钥"
 
-#: src/views/certificate/Certificate.vue:90
+#: src/views/certificate/Certificate.vue:98
 msgid "Not After"
 msgstr "有效期"
 
-#: src/routes/index.ts:223 src/routes/index.ts:225
+#: src/routes/index.ts:232 src/routes/index.ts:234
 msgid "Not Found"
 msgstr "找不到页面"
 
@@ -887,6 +918,14 @@ msgstr "此前无效: %{date}"
 msgid "Note"
 msgstr "注意"
 
+#: src/views/notification/Notification.vue:63
+msgid "Notification"
+msgstr "通知"
+
+#: src/components/Notification/Notification.vue:82 src/routes/index.ts:177
+msgid "Notifications"
+msgstr "通知"
+
 #: src/views/domain/cert/components/ObtainCert.vue:191
 msgid "Obtain certificate"
 msgstr "获取证书"
@@ -902,16 +941,18 @@ msgid "Offline"
 msgstr "离线"
 
 #: src/components/ChatGPT/ChatGPT.vue:278
+#: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:59
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:140
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:346
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:144
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:352
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:104
 #: src/views/domain/cert/components/ObtainCert.vue:149
 #: src/views/domain/components/Deploy.vue:23
 #: src/views/domain/components/RightSettings.vue:51
-#: src/views/domain/DomainList.vue:144
+#: src/views/domain/DomainList.vue:143
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:215
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:47
+#: src/views/notification/Notification.vue:72
 msgid "OK"
 msgstr "确定"
 
@@ -1017,7 +1058,7 @@ msgstr "请至少选择一个节点!"
 msgid "Pre-release"
 msgstr "预发布"
 
-#: src/routes/index.ts:184 src/views/preference/Preference.vue:85
+#: src/routes/index.ts:193 src/views/preference/Preference.vue:85
 msgid "Preference"
 msgstr "偏好设置"
 
@@ -1080,11 +1121,23 @@ msgstr "重载中"
 msgid "Reloading nginx"
 msgstr "正在重载 Nginx"
 
+#: src/components/Notification/Notification.vue:52
+msgid "Removed successfully"
+msgstr "删除成功"
+
 #: src/views/certificate/RenewCert.vue:43
 #: src/views/certificate/RenewCert.vue:47
 msgid "Renew Certificate"
 msgstr "更新证书"
 
+#: src/language/constants.ts:38
+msgid "Renew Certificate Error"
+msgstr "证书续期错误"
+
+#: src/language/constants.ts:37
+msgid "Renew Certificate Success"
+msgstr "证书续期成功"
+
 #: src/views/certificate/RenewCert.vue:25
 msgid "Renew successfully"
 msgstr "更新成功"
@@ -1114,7 +1167,7 @@ msgid "Running"
 msgstr "运行中"
 
 #: src/components/ChatGPT/ChatGPT.vue:259
-#: src/views/certificate/CertificateEditor.vue:175
+#: src/views/certificate/CertificateEditor.vue:212
 #: src/views/config/ConfigEdit.vue:98 src/views/domain/DomainEdit.vue:268
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:111
 #: src/views/preference/Preference.vue:113
@@ -1131,7 +1184,7 @@ msgid "Save error %{msg}"
 msgstr "保存错误 %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/views/certificate/CertificateEditor.vue:46
+#: src/views/certificate/CertificateEditor.vue:47
 #: src/views/preference/Preference.vue:58
 msgid "Save successfully"
 msgstr "保存成功"
@@ -1196,27 +1249,29 @@ msgstr "使用 HTTP01 challenge provider"
 msgid "Single Directive"
 msgstr "单行指令"
 
-#: src/routes/index.ts:159
+#: src/routes/index.ts:160
 msgid "Site Logs"
 msgstr "站点列表"
 
-#: src/routes/index.ts:66
+#: src/routes/index.ts:67
 msgid "Sites List"
 msgstr "站点列表"
 
-#: src/views/certificate/CertificateEditor.vue:135
+#: src/views/certificate/CertificateEditor.vue:172
 msgid "SSL Certificate Content"
 msgstr "SSL 证书内容"
 
-#: src/views/certificate/CertificateEditor.vue:142
+#: src/views/certificate/CertificateEditor.vue:179
 msgid "SSL Certificate Key Content"
 msgstr "SSL 证书密钥内容"
 
-#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/Certificate.vue:71
+#: src/views/certificate/CertificateEditor.vue:163
 msgid "SSL Certificate Key Path"
 msgstr "SSL证书密钥路径"
 
-#: src/views/certificate/Certificate.vue:55
+#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/CertificateEditor.vue:154
 msgid "SSL Certificate Path"
 msgstr "SSL证书路径"
 
@@ -1228,7 +1283,7 @@ msgstr "SSO 登录"
 msgid "Stable"
 msgstr "稳定"
 
-#: src/views/certificate/Certificate.vue:71 src/views/domain/DomainList.vue:25
+#: src/views/certificate/Certificate.vue:79 src/views/domain/DomainList.vue:25
 #: src/views/environment/Environment.vue:78
 msgid "Status"
 msgstr "状态"
@@ -1245,6 +1300,10 @@ msgstr "存储"
 msgid "Subject Name: %{subject}"
 msgstr "主体名称: %{subject}"
 
+#: src/constants/index.ts:20
+msgid "Success"
+msgstr "成功"
+
 #: src/views/dashboard/ServerAnalytic.vue:233
 msgid "Swap"
 msgstr "Swap"
@@ -1257,7 +1316,7 @@ msgstr "切换到深色主题"
 msgid "Switch to light theme"
 msgstr "切换到浅色"
 
-#: src/routes/index.ts:192
+#: src/routes/index.ts:201
 msgid "System"
 msgstr "系统"
 
@@ -1269,7 +1328,7 @@ msgstr "列表"
 msgid "Target"
 msgstr "目标"
 
-#: src/routes/index.ts:137 src/views/pty/Terminal.vue:95
+#: src/routes/index.ts:138 src/views/pty/Terminal.vue:95
 msgid "Terminal"
 msgstr "终端"
 
@@ -1299,11 +1358,11 @@ msgstr "当前配置中的 server_name 必须是获取证书所需的域名,
 msgid "The username or password is incorrect"
 msgstr "用户名或密码错误"
 
-#: src/views/certificate/CertificateEditor.vue:93
+#: src/views/certificate/CertificateEditor.vue:101
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "这个证书自动续期项目是无效的,请删除。"
 
-#: src/views/certificate/CertificateEditor.vue:83
+#: src/views/certificate/CertificateEditor.vue:91
 msgid "This certificate is managed by Nginx UI"
 msgstr "该证书由 Nginx UI 托管"
 
@@ -1311,6 +1370,10 @@ msgstr "该证书由 Nginx UI 托管"
 msgid "This field should not be empty"
 msgstr "该字段不能为空"
 
+#: src/views/notification/Notification.vue:21
+msgid "Title"
+msgstr "标题"
+
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:42
 msgid ""
 "To make sure the certification auto-renewal can work normally, we need to "
@@ -1322,6 +1385,7 @@ msgstr ""
 "请求的 Location,并且我们需要保存这个文件并重新加载Nginx。你确定要继续吗?"
 
 #: src/views/certificate/Certificate.vue:39 src/views/config/config.ts:14
+#: src/views/notification/Notification.vue:15
 msgid "Type"
 msgstr "类型"
 
@@ -1337,7 +1401,7 @@ msgstr "修改时间"
 msgid "Updated successfully"
 msgstr "更新成功"
 
-#: src/routes/index.ts:203 src/views/system/Upgrade.vue:143
+#: src/routes/index.ts:212 src/views/system/Upgrade.vue:143
 #: src/views/system/Upgrade.vue:235
 msgid "Upgrade"
 msgstr "升级"
@@ -1370,7 +1434,7 @@ msgstr "用户名"
 msgid "Username (*)"
 msgstr "用户名 (*)"
 
-#: src/views/certificate/Certificate.vue:80
+#: src/views/certificate/Certificate.vue:88
 msgid "Valid"
 msgstr "有效的"
 
@@ -1378,7 +1442,11 @@ msgstr "有效的"
 msgid "View"
 msgstr "查看"
 
-#: src/views/config/InspectConfig.vue:36
+#: src/components/Notification/Notification.vue:143
+msgid "View all notifications"
+msgstr "查看全部通知"
+
+#: src/constants/index.ts:18 src/views/config/InspectConfig.vue:36
 #: src/views/domain/cert/components/AutoCertStepOne.vue:23
 #: src/views/domain/DomainAdd.vue:114
 msgid "Warning"
@@ -1450,9 +1518,6 @@ msgstr "你可以在这个页面检查Nginx UI的升级。"
 #~ "或多个TXT记录,以进行所有权验证。一旦验证完成,这些记录将被删除。请注意,"
 #~ "下面的时间配置都是以秒为单位。"
 
-#~ msgid "Certification"
-#~ msgstr "证书"
-
 #~ msgid "Delete ID: %{id}"
 #~ msgstr "删除 ID: %{id}"
 

+ 143 - 68
app/src/language/zh_TW/app.po

@@ -14,30 +14,30 @@ msgstr ""
 "Generated-By: easygettext\n"
 "X-Generator: Poedit 3.4.1\n"
 
-#: src/routes/index.ts:199
+#: src/routes/index.ts:208
 msgid "About"
 msgstr "關於"
 
-#: src/routes/index.ts:151 src/views/domain/ngx_conf/LogEntry.vue:78
+#: src/routes/index.ts:152 src/views/domain/ngx_conf/LogEntry.vue:78
 msgid "Access Logs"
 msgstr "存取日誌"
 
-#: src/views/certificate/Certificate.vue:96
+#: src/views/certificate/Certificate.vue:104
 #: src/views/certificate/DNSCredential.vue:32 src/views/config/config.ts:36
 #: src/views/domain/DomainList.vue:50 src/views/environment/Environment.vue:105
-#: src/views/user/User.vue:46
+#: src/views/notification/Notification.vue:38 src/views/user/User.vue:46
 msgid "Action"
 msgstr "操作"
 
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:114
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:115
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
+#: src/views/certificate/Certificate.vue:120
 #: src/views/domain/ngx_conf/config_template/ConfigTemplate.vue:115
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:308
 msgid "Add"
 msgstr "新增"
 
-#: src/routes/index.ts:122 src/views/certificate/Certificate.vue:115
-#: src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:123 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Add Certificate"
 msgstr "憑證狀態"
@@ -51,7 +51,7 @@ msgstr "在下方新增指令"
 msgid "Add Location"
 msgstr "新增 Location"
 
-#: src/routes/index.ts:70 src/views/domain/DomainAdd.vue:91
+#: src/routes/index.ts:71 src/views/domain/DomainAdd.vue:91
 msgid "Add Site"
 msgstr "新增網站"
 
@@ -79,12 +79,18 @@ msgstr "API Token"
 msgid "Arch"
 msgstr "架構"
 
+#: src/components/Notification/Notification.vue:86
+#: src/views/notification/Notification.vue:73
+#, fuzzy
+msgid "Are you sure you want to clear all notifications?"
+msgstr "您確定要清除聊天記錄嗎?"
+
 #: src/components/ChatGPT/ChatGPT.vue:279
 msgid "Are you sure you want to clear the record of chat?"
 msgstr "您確定要清除聊天記錄嗎?"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:347
-#: src/views/domain/DomainList.vue:145
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:353
+#: src/views/domain/DomainList.vue:144
 msgid "Are you sure you want to delete?"
 msgstr "您確定要刪除嗎?"
 
@@ -125,7 +131,7 @@ msgstr "已關閉 %{name} 的自動續簽"
 msgid "Auto-renewal enabled for %{name}"
 msgstr "已啟用 %{name} 的自動續簽"
 
-#: src/views/certificate/CertificateEditor.vue:168
+#: src/views/certificate/CertificateEditor.vue:205
 #: src/views/config/Config.vue:75 src/views/config/ConfigEdit.vue:89
 #: src/views/domain/DomainEdit.vue:261 src/views/nginx_log/NginxLog.vue:170
 msgid "Back"
@@ -164,7 +170,7 @@ msgstr "CADir"
 
 #: src/components/ChatGPT/ChatGPT.vue:260
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:58
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:139
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:143
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:103
 #: src/views/domain/cert/components/ObtainCert.vue:150
 #: src/views/domain/components/Deploy.vue:24
@@ -182,17 +188,17 @@ msgstr "此憑證已過期"
 msgid "Certificate is valid"
 msgstr "此憑證有效"
 
-#: src/views/certificate/CertificateEditor.vue:119
+#: src/views/certificate/CertificateEditor.vue:127
 #: src/views/domain/cert/Cert.vue:35
 msgid "Certificate Status"
 msgstr "憑證狀態"
 
-#: src/routes/index.ts:100 src/views/certificate/Certificate.vue:109
+#: src/routes/index.ts:101 src/views/certificate/Certificate.vue:117
 #, fuzzy
 msgid "Certificates"
 msgstr "憑證狀態"
 
-#: src/routes/index.ts:109
+#: src/routes/index.ts:110
 #, fuzzy
 msgid "Certificates List"
 msgstr "憑證清單"
@@ -227,9 +233,17 @@ msgid "Cleaning environment variables"
 msgstr "清理環境變數"
 
 #: src/components/ChatGPT/ChatGPT.vue:283
+#: src/components/Notification/Notification.vue:91
+#: src/views/notification/Notification.vue:78
 msgid "Clear"
 msgstr "清除"
 
+#: src/components/Notification/Notification.vue:42
+#: src/views/notification/Notification.vue:47
+#, fuzzy
+msgid "Cleared successfully"
+msgstr "成功停用"
+
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:97
 #: src/views/domain/ngx_conf/LocationEditor.vue:121
 #: src/views/domain/ngx_conf/LocationEditor.vue:90
@@ -283,7 +297,7 @@ msgstr "中央處理器:"
 msgid "Create Another"
 msgstr "再建立一個"
 
-#: src/views/user/User.vue:34
+#: src/views/notification/Notification.vue:32 src/views/user/User.vue:34
 msgid "Created at"
 msgstr "建立時間"
 
@@ -308,7 +322,7 @@ msgstr "目前版本"
 msgid "Custom"
 msgstr "自訂"
 
-#: src/routes/index.ts:51
+#: src/routes/index.ts:52
 msgid "Dashboard"
 msgstr "儀表板"
 
@@ -316,7 +330,7 @@ msgstr "儀表板"
 msgid "Database (Optional, default: database)"
 msgstr "資料庫 (可選,預設: database)"
 
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:354
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:360
 #: src/views/domain/DomainList.vue:153
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:259
 msgid "Delete"
@@ -353,6 +367,10 @@ msgstr "部署成功"
 msgid "Description"
 msgstr "描述"
 
+#: src/views/notification/Notification.vue:28
+msgid "Details"
+msgstr ""
+
 #: src/views/system/About.vue:25
 msgid "Development Mode"
 msgstr "開發模式"
@@ -379,7 +397,7 @@ msgid "Disable auto-renewal failed for %{name}"
 msgstr "關閉 %{name} 自動續簽失敗"
 
 #: src/views/domain/cert/ChangeCert.vue:48 src/views/domain/DomainEdit.vue:190
-#: src/views/domain/DomainList.vue:123
+#: src/views/domain/DomainList.vue:122
 msgid "Disabled"
 msgstr "停用"
 
@@ -392,7 +410,7 @@ msgstr "成功停用"
 msgid "Disk IO"
 msgstr "磁碟 IO"
 
-#: src/routes/index.ts:130 src/views/certificate/DNSCredential.vue:39
+#: src/routes/index.ts:131 src/views/certificate/DNSCredential.vue:39
 msgid "DNS Credentials"
 msgstr "DNS 認證"
 
@@ -434,7 +452,7 @@ msgstr "您要移除此伺服器嗎?"
 msgid "Domain Config Created Successfully"
 msgstr "網域設定檔成功建立"
 
-#: src/views/certificate/CertificateEditor.vue:103
+#: src/views/certificate/CertificateEditor.vue:111
 #, fuzzy
 msgid "Domains list is empty, try to reopen Auto Cert for %{config}"
 msgstr "網域列表為空,請嘗試重新開啟 %{config} 的自動憑證"
@@ -452,7 +470,7 @@ msgid "Dry run mode enabled"
 msgstr "試運轉模式已啟用"
 
 #: src/views/domain/components/SiteDuplicate.vue:128
-#: src/views/domain/DomainList.vue:139
+#: src/views/domain/DomainList.vue:138
 msgid "Duplicate"
 msgstr "複製"
 
@@ -476,11 +494,11 @@ msgstr "成功複製至本機"
 msgid "Edit %{n}"
 msgstr "編輯 %{n}"
 
-#: src/routes/index.ts:92 src/views/config/ConfigEdit.vue:83
+#: src/routes/index.ts:93 src/views/config/ConfigEdit.vue:83
 msgid "Edit Configuration"
 msgstr "編輯設定"
 
-#: src/routes/index.ts:74
+#: src/routes/index.ts:75
 msgid "Edit Site"
 msgstr "編輯網站"
 
@@ -519,7 +537,7 @@ msgstr "啟用 TLS"
 #: src/views/domain/cert/ChangeCert.vue:44
 #: src/views/domain/components/Deploy.vue:89
 #: src/views/domain/components/RightSettings.vue:75
-#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:131
+#: src/views/domain/DomainEdit.vue:184 src/views/domain/DomainList.vue:130
 msgid "Enabled"
 msgstr "已啟用"
 
@@ -533,7 +551,7 @@ msgstr "成功啟用"
 msgid "Encrypt website with Let's Encrypt"
 msgstr "用 Let's Encrypt 對網站進行加密"
 
-#: src/routes/index.ts:168 src/views/environment/Environment.vue:113
+#: src/routes/index.ts:169 src/views/environment/Environment.vue:113
 msgid "Environment"
 msgstr "環境"
 
@@ -541,11 +559,11 @@ msgstr "環境"
 msgid "Environments"
 msgstr "環境"
 
-#: src/views/config/InspectConfig.vue:47
+#: src/constants/index.ts:17 src/views/config/InspectConfig.vue:47
 msgid "Error"
 msgstr "錯誤"
 
-#: src/routes/index.ts:155 src/views/domain/ngx_conf/LogEntry.vue:86
+#: src/routes/index.ts:156 src/views/domain/ngx_conf/LogEntry.vue:86
 msgid "Error Logs"
 msgstr "錯誤日誌"
 
@@ -553,7 +571,7 @@ msgstr "錯誤日誌"
 msgid "Executable Path"
 msgstr "可執行檔路徑"
 
-#: src/views/certificate/Certificate.vue:84
+#: src/views/certificate/Certificate.vue:92
 msgid "Expired"
 msgstr ""
 
@@ -617,6 +635,11 @@ msgstr "格式錯誤 %{msg}"
 msgid "Format successfully"
 msgstr "成功格式化"
 
+#: src/views/certificate/Certificate.vue:45
+#, fuzzy
+msgid "General Certificate"
+msgstr "更換憑證"
+
 #: src/components/StdDesign/StdDataEntry/components/StdPassword.vue:60
 msgid "Generate"
 msgstr "產生"
@@ -653,7 +676,7 @@ msgstr "GPT-4-32K"
 msgid "GPT-4-Turbo"
 msgstr "GPT-4-Turbo"
 
-#: src/routes/index.ts:44
+#: src/routes/index.ts:45
 msgid "Home"
 msgstr "首頁"
 
@@ -673,6 +696,10 @@ msgstr "HTTP 監聽埠"
 msgid "HTTP01"
 msgstr "HTTP01"
 
+#: src/constants/index.ts:19
+msgid "Info"
+msgstr ""
+
 #: src/language/constants.ts:27
 msgid "Initial core upgrader error"
 msgstr "初始化核心升級程式錯誤"
@@ -681,7 +708,7 @@ msgstr "初始化核心升級程式錯誤"
 msgid "Initialing core upgrader"
 msgstr "正在初始化核心升級程式"
 
-#: src/routes/index.ts:211 src/views/other/Install.vue:139
+#: src/routes/index.ts:220 src/views/other/Install.vue:139
 msgid "Install"
 msgstr "安裝"
 
@@ -740,12 +767,12 @@ msgstr "Location"
 msgid "Locations"
 msgstr "Locations"
 
-#: src/views/certificate/CertificateEditor.vue:156
+#: src/views/certificate/CertificateEditor.vue:193
 #, fuzzy
 msgid "Log"
 msgstr "登入"
 
-#: src/routes/index.ts:217 src/views/other/Login.vue:147
+#: src/routes/index.ts:226 src/views/other/Login.vue:147
 msgid "Login"
 msgstr "登入"
 
@@ -753,7 +780,7 @@ msgstr "登入"
 msgid "Login successful"
 msgstr "登入成功"
 
-#: src/layouts/HeaderLayout.vue:21
+#: src/layouts/HeaderLayout.vue:22
 msgid "Logout successful"
 msgstr "登出成功"
 
@@ -764,18 +791,23 @@ msgid ""
 msgstr ""
 "在取得憑證前,請確保您已將 .well-known 目錄反向代理到 HTTPChallengePort。"
 
-#: src/routes/index.ts:83
+#: src/routes/index.ts:84
 msgid "Manage Configs"
 msgstr "管理設定"
 
-#: src/routes/index.ts:58 src/views/domain/DomainList.vue:102
+#: src/routes/index.ts:59 src/views/domain/DomainList.vue:102
 msgid "Manage Sites"
 msgstr "管理網站"
 
-#: src/routes/index.ts:176 src/views/user/User.vue:53
+#: src/routes/index.ts:185 src/views/user/User.vue:53
 msgid "Manage Users"
 msgstr "管理使用者"
 
+#: src/views/certificate/Certificate.vue:44
+#, fuzzy
+msgid "Managed Certificate"
+msgstr "更換憑證"
+
 #: src/views/dashboard/ServerAnalytic.vue:220
 msgid "Memory"
 msgstr "記憶體"
@@ -785,12 +817,12 @@ msgid "Memory and Storage"
 msgstr "記憶體與儲存"
 
 #: src/components/ChatGPT/ChatGPT.vue:256
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:137
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:141
 #: src/components/StdDesign/StdDataDisplay/StdTable.vue:336
 msgid "Modify"
 msgstr "修改"
 
-#: src/routes/index.ts:114 src/views/certificate/CertificateEditor.vue:76
+#: src/routes/index.ts:115 src/views/certificate/CertificateEditor.vue:84
 #, fuzzy
 msgid "Modify Certificate"
 msgstr "憑證狀態"
@@ -804,6 +836,7 @@ msgid "Multi-line Directive"
 msgstr "多行指令"
 
 #: src/views/certificate/Certificate.vue:22
+#: src/views/certificate/CertificateEditor.vue:145
 #: src/views/certificate/DNSCredential.vue:13 src/views/config/config.ts:9
 #: src/views/domain/cert/ChangeCert.vue:21
 #: src/views/domain/components/RightSettings.vue:81
@@ -857,7 +890,7 @@ msgstr "Nginx 控制元件"
 msgid "Nginx Error Log Path"
 msgstr "Nginx 錯誤日誌路徑"
 
-#: src/routes/index.ts:145 src/views/nginx_log/NginxLog.vue:145
+#: src/routes/index.ts:146 src/views/nginx_log/NginxLog.vue:145
 msgid "Nginx Log"
 msgstr "Nginx 日誌"
 
@@ -870,10 +903,12 @@ msgid "Nginx restarted successfully"
 msgstr "Nginx 重啟成功"
 
 #: src/components/ChatGPT/ChatGPT.vue:277
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:345
-#: src/views/domain/DomainList.vue:143
+#: src/components/Notification/Notification.vue:84
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:351
+#: src/views/domain/DomainList.vue:142
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:81
 #: src/views/domain/ngx_conf/LocationEditor.vue:76
+#: src/views/notification/Notification.vue:71
 msgid "No"
 msgstr "取消"
 
@@ -881,11 +916,11 @@ msgstr "取消"
 msgid "Node Secret"
 msgstr "Node Secret"
 
-#: src/views/certificate/Certificate.vue:90
+#: src/views/certificate/Certificate.vue:98
 msgid "Not After"
 msgstr ""
 
-#: src/routes/index.ts:223 src/routes/index.ts:225
+#: src/routes/index.ts:232 src/routes/index.ts:234
 msgid "Not Found"
 msgstr "找不到頁面"
 
@@ -898,6 +933,16 @@ msgstr "此前無效: %{date}"
 msgid "Note"
 msgstr "備註"
 
+#: src/views/notification/Notification.vue:63
+#, fuzzy
+msgid "Notification"
+msgstr "憑證"
+
+#: src/components/Notification/Notification.vue:82 src/routes/index.ts:177
+#, fuzzy
+msgid "Notifications"
+msgstr "憑證"
+
 #: src/views/domain/cert/components/ObtainCert.vue:191
 msgid "Obtain certificate"
 msgstr "取得憑證"
@@ -913,16 +958,18 @@ msgid "Offline"
 msgstr "離線"
 
 #: src/components/ChatGPT/ChatGPT.vue:278
+#: src/components/Notification/Notification.vue:85
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:59
-#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:140
-#: src/components/StdDesign/StdDataDisplay/StdTable.vue:346
+#: src/components/StdDesign/StdDataDisplay/StdCurd.vue:144
+#: src/components/StdDesign/StdDataDisplay/StdTable.vue:352
 #: src/components/StdDesign/StdDataEntry/components/StdSelector.vue:104
 #: src/views/domain/cert/components/ObtainCert.vue:149
 #: src/views/domain/components/Deploy.vue:23
 #: src/views/domain/components/RightSettings.vue:51
-#: src/views/domain/DomainList.vue:144
+#: src/views/domain/DomainList.vue:143
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:215
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:47
+#: src/views/notification/Notification.vue:72
 msgid "OK"
 msgstr "確定"
 
@@ -1029,7 +1076,7 @@ msgstr "請至少選擇一個節點!"
 msgid "Pre-release"
 msgstr "預先發布"
 
-#: src/routes/index.ts:184 src/views/preference/Preference.vue:85
+#: src/routes/index.ts:193 src/views/preference/Preference.vue:85
 msgid "Preference"
 msgstr "偏好設定"
 
@@ -1092,12 +1139,27 @@ msgstr "重新載入中"
 msgid "Reloading nginx"
 msgstr "正在重新載入 Nginx"
 
+#: src/components/Notification/Notification.vue:52
+#, fuzzy
+msgid "Removed successfully"
+msgstr "儲存成功"
+
 #: src/views/certificate/RenewCert.vue:43
 #: src/views/certificate/RenewCert.vue:47
 #, fuzzy
 msgid "Renew Certificate"
 msgstr "更換憑證"
 
+#: src/language/constants.ts:38
+#, fuzzy
+msgid "Renew Certificate Error"
+msgstr "更換憑證"
+
+#: src/language/constants.ts:37
+#, fuzzy
+msgid "Renew Certificate Success"
+msgstr "更換憑證"
+
 #: src/views/certificate/RenewCert.vue:25
 #, fuzzy
 msgid "Renew successfully"
@@ -1128,7 +1190,7 @@ msgid "Running"
 msgstr "執行中"
 
 #: src/components/ChatGPT/ChatGPT.vue:259
-#: src/views/certificate/CertificateEditor.vue:175
+#: src/views/certificate/CertificateEditor.vue:212
 #: src/views/config/ConfigEdit.vue:98 src/views/domain/DomainEdit.vue:268
 #: src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue:111
 #: src/views/preference/Preference.vue:113
@@ -1145,7 +1207,7 @@ msgid "Save error %{msg}"
 msgstr "儲存錯誤 %{msg}"
 
 #: src/components/StdDesign/StdDataDisplay/StdBatchEdit.vue:42
-#: src/views/certificate/CertificateEditor.vue:46
+#: src/views/certificate/CertificateEditor.vue:47
 #: src/views/preference/Preference.vue:58
 msgid "Save successfully"
 msgstr "儲存成功"
@@ -1212,29 +1274,31 @@ msgstr "使用 HTTP01 挑戰提供者"
 msgid "Single Directive"
 msgstr "單一指令"
 
-#: src/routes/index.ts:159
+#: src/routes/index.ts:160
 msgid "Site Logs"
 msgstr "網站日誌"
 
-#: src/routes/index.ts:66
+#: src/routes/index.ts:67
 msgid "Sites List"
 msgstr "網站列表"
 
-#: src/views/certificate/CertificateEditor.vue:135
+#: src/views/certificate/CertificateEditor.vue:172
 #, fuzzy
 msgid "SSL Certificate Content"
 msgstr "SSL 認證內容"
 
-#: src/views/certificate/CertificateEditor.vue:142
+#: src/views/certificate/CertificateEditor.vue:179
 #, fuzzy
 msgid "SSL Certificate Key Content"
 msgstr "SSL 憑證金鑰內容"
 
-#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/Certificate.vue:71
+#: src/views/certificate/CertificateEditor.vue:163
 msgid "SSL Certificate Key Path"
 msgstr "SSL 憑證金鑰路徑"
 
-#: src/views/certificate/Certificate.vue:55
+#: src/views/certificate/Certificate.vue:63
+#: src/views/certificate/CertificateEditor.vue:154
 msgid "SSL Certificate Path"
 msgstr "SSL 憑證路徑"
 
@@ -1247,7 +1311,7 @@ msgstr "登入"
 msgid "Stable"
 msgstr "穩定"
 
-#: src/views/certificate/Certificate.vue:71 src/views/domain/DomainList.vue:25
+#: src/views/certificate/Certificate.vue:79 src/views/domain/DomainList.vue:25
 #: src/views/environment/Environment.vue:78
 msgid "Status"
 msgstr "狀態"
@@ -1265,6 +1329,10 @@ msgstr "儲存空間"
 msgid "Subject Name: %{subject}"
 msgstr "主體名稱: %{name}"
 
+#: src/constants/index.ts:20
+msgid "Success"
+msgstr ""
+
 #: src/views/dashboard/ServerAnalytic.vue:233
 msgid "Swap"
 msgstr "交換空間"
@@ -1277,7 +1345,7 @@ msgstr ""
 msgid "Switch to light theme"
 msgstr ""
 
-#: src/routes/index.ts:192
+#: src/routes/index.ts:201
 msgid "System"
 msgstr "系統"
 
@@ -1289,7 +1357,7 @@ msgstr "表格"
 msgid "Target"
 msgstr "目標"
 
-#: src/routes/index.ts:137 src/views/pty/Terminal.vue:95
+#: src/routes/index.ts:138 src/views/pty/Terminal.vue:95
 msgid "Terminal"
 msgstr "終端機"
 
@@ -1321,12 +1389,12 @@ msgstr "注意:目前設定中的 server_name 必須為需要申請憑證的
 msgid "The username or password is incorrect"
 msgstr "使用者名稱或密碼不正確"
 
-#: src/views/certificate/CertificateEditor.vue:93
+#: src/views/certificate/CertificateEditor.vue:101
 #, fuzzy
 msgid "This Auto Cert item is invalid, please remove it."
 msgstr "此自動憑證項目無效,請將其移除。"
 
-#: src/views/certificate/CertificateEditor.vue:83
+#: src/views/certificate/CertificateEditor.vue:91
 msgid "This certificate is managed by Nginx UI"
 msgstr ""
 
@@ -1334,6 +1402,10 @@ msgstr ""
 msgid "This field should not be empty"
 msgstr "此欄位不應為空"
 
+#: src/views/notification/Notification.vue:21
+msgid "Title"
+msgstr ""
+
 #: src/views/domain/ngx_conf/NgxConfigEditor.vue:42
 msgid ""
 "To make sure the certification auto-renewal can work normally, we need to "
@@ -1345,6 +1417,7 @@ msgstr ""
 "的請求,我們需要儲存這個檔案並重新載入 Nginx。你確定你要繼續嗎?"
 
 #: src/views/certificate/Certificate.vue:39 src/views/config/config.ts:14
+#: src/views/notification/Notification.vue:15
 msgid "Type"
 msgstr "類型"
 
@@ -1360,7 +1433,7 @@ msgstr "更新時間"
 msgid "Updated successfully"
 msgstr "更新成功"
 
-#: src/routes/index.ts:203 src/views/system/Upgrade.vue:143
+#: src/routes/index.ts:212 src/views/system/Upgrade.vue:143
 #: src/views/system/Upgrade.vue:235
 msgid "Upgrade"
 msgstr "升級"
@@ -1393,7 +1466,7 @@ msgstr "使用者名稱"
 msgid "Username (*)"
 msgstr "使用者名稱 (*)"
 
-#: src/views/certificate/Certificate.vue:80
+#: src/views/certificate/Certificate.vue:88
 msgid "Valid"
 msgstr ""
 
@@ -1401,7 +1474,12 @@ msgstr ""
 msgid "View"
 msgstr "檢視"
 
-#: src/views/config/InspectConfig.vue:36
+#: src/components/Notification/Notification.vue:143
+#, fuzzy
+msgid "View all notifications"
+msgstr "憑證"
+
+#: src/constants/index.ts:18 src/views/config/InspectConfig.vue:36
 #: src/views/domain/cert/components/AutoCertStepOne.vue:23
 #: src/views/domain/DomainAdd.vue:114
 msgid "Warning"
@@ -1478,9 +1556,6 @@ msgstr "您可以在此頁面檢查 Nginx UI 的升級。"
 #~ "新增到您網域的 DNS 記錄中以進行所有權驗證。驗證完成後,記錄將被刪除。請注"
 #~ "意,以下時間設定均以秒為單位。"
 
-#~ msgid "Certification"
-#~ msgstr "憑證"
-
 #~ msgid "Delete ID: %{id}"
 #~ msgstr "刪除 ID: %{id}"
 

+ 3 - 0
app/src/layouts/HeaderLayout.vue

@@ -7,6 +7,7 @@ import gettext from '@/gettext'
 import auth from '@/api/auth'
 import NginxControl from '@/components/NginxControl/NginxControl.vue'
 import SwitchAppearance from '@/components/SwitchAppearance/SwitchAppearance.vue'
+import Notification from '@/components/Notification/Notification.vue'
 
 const emit = defineEmits<{
   clickUnFold: [void]
@@ -39,6 +40,8 @@ function logout() {
 
       <SwitchAppearance />
 
+      <Notification />
+
       <a href="/">
         <HomeOutlined />
       </a>

+ 1 - 0
app/src/pinia/moudule/user.ts

@@ -3,6 +3,7 @@ import { defineStore } from 'pinia'
 export const useUserStore = defineStore('user', {
   state: () => ({
     token: '',
+    unreadCount: 0,
   }),
   getters: {
     is_login(state): boolean {

+ 9 - 0
app/src/routes/index.ts

@@ -2,6 +2,7 @@ import { createRouter, createWebHashHistory } from 'vue-router'
 import type { AntDesignOutlinedIconType } from '@ant-design/icons-vue/lib/icons/AntDesignOutlined'
 
 import {
+  BellOutlined,
   CloudOutlined,
   CodeOutlined,
   DatabaseOutlined,
@@ -171,6 +172,14 @@ export const routes: Route[] = [
           icon: DatabaseOutlined,
         },
       },
+      {
+        path: 'notifications',
+        name: () => $gettext('Notifications'),
+        component: () => import('@/views/notification/Notification.vue'),
+        meta: {
+          icon: BellOutlined,
+        },
+      },
       {
         path: 'user',
         name: () => $gettext('Manage Users'),

+ 12 - 4
app/src/views/certificate/Certificate.vue

@@ -41,11 +41,19 @@ const columns: Column[] = [{
   customRender: (args: customRender) => {
     const template = []
     const { text } = args
-    if (text === true || text > 0)
-      template.push(<Tag bordered={false} color="processing">{$gettext('Managed Certificate')}</Tag>)
+    const managed = $gettext('Managed Certificate')
+    const general = $gettext('General Certificate')
+    if (text === true || text > 0) {
+      template.push(<Tag bordered={false} color="processing">
+        { managed }
+      </Tag>)
+    }
 
-    else
-      template.push(<Tag bordered={false} color="purple">{$gettext('General Certificate')}</Tag>)
+    else {
+      template.push(<Tag bordered={false} color="purple">{
+      general }
+      </Tag>)
+    }
 
     return h('div', template)
   },

+ 40 - 12
app/src/views/certificate/CertificateEditor.vue

@@ -40,10 +40,14 @@ onMounted(() => {
   init()
 })
 
+const router = useRouter()
 function save() {
   cert.save(data.value.id, data.value).then(r => {
     data.value = r
     message.success($gettext('Save successfully'))
+    router.push(`/certificates/${r.id}`)
+  }).catch(e => {
+    message.error($gettext(e?.message ?? 'Server error'))
   })
 }
 
@@ -70,12 +74,16 @@ const log = computed(() => {
 
   return logs.join('\n')
 })
+
+const isManaged = computed(() => {
+  return data.value.auto_cert === AutoCertState.Enable
+})
 </script>
 
 <template>
   <ACard :title="id > 0 ? $gettext('Modify Certificate') : $gettext('Add Certificate')">
     <div
-      v-if="data.auto_cert === AutoCertState.Enable"
+      v-if="isManaged"
       class="mb-4"
     >
       <div class="mb-2">
@@ -121,25 +129,45 @@ const log = computed(() => {
           </AFormItem>
         </AForm>
 
-        <RenewCert
-          v-if="data.auto_cert === AutoCertState.Enable"
-          @renewed="init"
-        />
+        <template v-if="isManaged">
+          <RenewCert @renewed="init" />
+
+          <AutoCertStepOne
+            style="max-width: 600px"
+            hide-note
+          />
+        </template>
 
-        <AutoCertStepOne
-          v-if="data.auto_cert === AutoCertState.Enable"
-          style="max-width: 600px"
-          hide-note
-        />
         <AForm
           layout="vertical"
           style="max-width: 600px"
         >
+          <AFormItem :label="$gettext('Name')">
+            <p v-if="isManaged">
+              {{ data.name }}
+            </p>
+            <AInput
+              v-else
+              v-model:value="data.name"
+            />
+          </AFormItem>
           <AFormItem :label="$gettext('SSL Certificate Path')">
-            <AInput v-model:value="data.ssl_certificate_path" />
+            <p v-if="isManaged">
+              {{ data.ssl_certificate_path }}
+            </p>
+            <AInput
+              v-else
+              v-model:value="data.ssl_certificate_path"
+            />
           </AFormItem>
           <AFormItem :label="$gettext('SSL Certificate Key Path')">
-            <AInput v-model:value="data.ssl_certificate_key_path" />
+            <p v-if="isManaged">
+              {{ data.ssl_certificate_key_path }}
+            </p>
+            <AInput
+              v-else
+              v-model:value="data.ssl_certificate_key_path"
+            />
           </AFormItem>
           <AFormItem :label="$gettext('SSL Certificate Content')">
             <CodeEditor

+ 0 - 1
app/src/views/domain/DomainList.vue

@@ -113,7 +113,6 @@ watch(route, () => {
       })"
     >
       <template #actions="{ record }">
-        <ADivider type="vertical" />
         <AButton
           v-if="record.enabled"
           type="link"

+ 87 - 0
app/src/views/notification/Notification.vue

@@ -0,0 +1,87 @@
+<script setup lang="ts">
+import { useGettext } from 'vue3-gettext'
+import { message } from 'ant-design-vue'
+import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
+import notification from '@/api/notification'
+import type { Column } from '@/components/StdDesign/types'
+import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import { datetime, mask } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
+import { NotificationType } from '@/constants'
+import { useUserStore } from '@/pinia'
+
+const { $gettext } = useGettext()
+
+const columns: Column[] = [{
+  title: () => $gettext('Type'),
+  dataIndex: 'type',
+  customRender: (args: customRender) => mask(args, NotificationType),
+  sortable: true,
+  pithy: true,
+}, {
+  title: () => $gettext('Title'),
+  dataIndex: 'title',
+  customRender: (args: customRender) => {
+    return h('span', $gettext(args.text))
+  },
+  pithy: true,
+}, {
+  title: () => $gettext('Details'),
+  dataIndex: 'details',
+  pithy: true,
+}, {
+  title: () => $gettext('Created at'),
+  dataIndex: 'created_at',
+  sortable: true,
+  customRender: datetime,
+  pithy: true,
+}, {
+  title: () => $gettext('Action'),
+  dataIndex: 'action',
+}]
+
+const { unreadCount } = storeToRefs(useUserStore())
+
+const curd = ref()
+function clear() {
+  notification.clear().then(() => {
+    message.success($gettext('Cleared successfully'))
+    curd.value.get_list()
+    unreadCount.value = 0
+  }).catch(e => {
+    message.error($gettext(e?.message ?? 'Server error'))
+  })
+}
+
+watch(unreadCount, () => {
+  curd.value.get_list()
+})
+</script>
+
+<template>
+  <StdCurd
+    ref="curd"
+    :title="$gettext('Notification')"
+    :columns="columns"
+    :api="notification"
+    disabled-modify
+    disable-add
+  >
+    <template #extra>
+      <APopconfirm
+        :cancel-text="$gettext('No')"
+        :ok-text="$gettext('OK')"
+        :title="$gettext('Are you sure you want to clear all notifications?')"
+        placement="bottomRight"
+        @confirm="clear"
+      >
+        <a>
+          {{ $gettext('Clear') }}
+        </a>
+      </APopconfirm>
+    </template>
+  </StdCurd>
+</template>
+
+<style scoped lang="less">
+
+</style>

+ 1 - 1
cmd/generate/generate.go

@@ -17,7 +17,7 @@ func main() {
 	// specify the output directory (default: "./query")
 	// ### if you want to query without context constrain, set mode gen.WithoutContext ###
 	g := gen.NewGenerator(gen.Config{
-		OutPath: "../../server/query",
+		OutPath: "../../query",
 		Mode:    gen.WithoutContext | gen.WithDefaultQuery,
 		//if you want the nullable field generation property to be pointer type, set FieldNullable true
 		FieldNullable: true,

+ 28 - 12
internal/cert/auto_cert.go

@@ -2,8 +2,10 @@ package cert
 
 import (
 	"github.com/0xJacky/Nginx-UI/internal/logger"
+	"github.com/0xJacky/Nginx-UI/internal/notification"
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/pkg/errors"
+	"runtime"
 	"strings"
 	"time"
 )
@@ -11,7 +13,9 @@ import (
 func AutoObtain() {
 	defer func() {
 		if err := recover(); err != nil {
-			logger.Error("AutoCert Recover", err)
+			buf := make([]byte, 1024)
+			runtime.Stack(buf, false)
+			logger.Error("AutoCert Recover", err, string(buf))
 		}
 	}()
 	logger.Info("AutoCert Worker Started")
@@ -38,21 +42,29 @@ func renew(certModel *model.Cert) {
 	if len(certModel.Domains) == 0 {
 		log.Error(errors.New("domains list is empty, " +
 			"try to reopen auto-cert for this config:" + confName))
+		notification.Error("Renew Certificate Error", confName)
 		return
 	}
 
-	if certModel.SSLCertificatePath != "" {
-		cert, err := GetCertInfo(certModel.SSLCertificatePath)
-		if err != nil {
-			// Get certificate info error, ignore this certificate
-			log.Error(errors.Wrap(err, "get certificate info error"))
-			return
-		}
-		if time.Now().Sub(cert.NotBefore).Hours()/24 < 7 {
-			// not between 1 week, ignore this certificate
-			return
-		}
+	if certModel.SSLCertificatePath == "" {
+		log.Error(errors.New("ssl certificate path is empty, " +
+			"try to reopen auto-cert for this config:" + confName))
+		notification.Error("Renew Certificate Error", confName)
+		return
+	}
+
+	cert, err := GetCertInfo(certModel.SSLCertificatePath)
+	if err != nil {
+		// Get certificate info error, ignore this certificate
+		log.Error(errors.Wrap(err, "get certificate info error"))
+		notification.Error("Renew Certificate Error", strings.Join(certModel.Domains, ", "))
+		return
+	}
+	if time.Now().Sub(cert.NotBefore).Hours()/24 < 7 {
+		// not between 1 week, ignore this certificate
+		return
 	}
+
 	// after 1 mo, reissue certificate
 	logChan := make(chan string, 1)
 	errChan := make(chan error, 1)
@@ -76,5 +88,9 @@ func renew(certModel *model.Cert) {
 	// block, unless errChan closed
 	for err := range errChan {
 		log.Error(err)
+		notification.Error("Renew Certificate Error", strings.Join(payload.ServerName, ", "))
+		return
 	}
+
+	notification.Success("Renew Certificate Success", strings.Join(payload.ServerName, ", "))
 }

+ 32 - 0
internal/notification/notification.go

@@ -0,0 +1,32 @@
+package notification
+
+import (
+	"github.com/0xJacky/Nginx-UI/model"
+	"github.com/0xJacky/Nginx-UI/query"
+)
+
+func Info(title string, details string) {
+	push(model.NotificationInfo, title, details)
+}
+
+func Error(title string, details string) {
+	push(model.NotificationError, title, details)
+}
+
+func Warning(title string, details string) {
+	push(model.NotificationWarning, title, details)
+}
+
+func Success(title string, details string) {
+	push(model.NotificationSuccess, title, details)
+}
+
+func push(nType model.NotificationType, title string, details string) {
+	n := query.Notification
+
+	_ = n.Create(&model.Notification{
+		Type:    nType,
+		Title:   title,
+		Details: details,
+	})
+}

+ 3 - 6
model/model.go

@@ -34,6 +34,7 @@ func GenerateAllModel() []any {
 		Site{},
 		DnsCredential{},
 		Environment{},
+		Notification{},
 	}
 }
 
@@ -99,12 +100,8 @@ func OrderAndPaginate(c *gin.Context) func(db *gorm.DB) *gorm.DB {
 	return func(db *gorm.DB) *gorm.DB {
 		sort := c.DefaultQuery("order", "desc")
 
-		sortBy := DefaultQuery(c, "sort_by", "")
-
-		if sortBy != "" {
-			order := fmt.Sprintf("`%s` %s", DefaultQuery(c, "sort_by", "id"), sort)
-			db = db.Order(order)
-		}
+		order := fmt.Sprintf("`%s` %s", DefaultQuery(c, "sort_by", "id"), sort)
+		db = db.Order(order)
 
 		page := cast.ToInt(c.Query("page"))
 		if page == 0 {

+ 17 - 0
model/notification.go

@@ -0,0 +1,17 @@
+package model
+
+type NotificationType int
+
+const (
+	NotificationError NotificationType = iota
+	NotificationWarning
+	NotificationInfo
+	NotificationSuccess
+)
+
+type Notification struct {
+	Model
+	Type    NotificationType `json:"type"`
+	Title   string           `json:"title"`
+	Details string           `json:"details"`
+}

+ 2 - 5
query/auth_tokens.gen.go

@@ -6,7 +6,6 @@ package query
 
 import (
 	"context"
-	"github.com/0xJacky/Nginx-UI/model"
 	"strings"
 
 	"gorm.io/gorm"
@@ -17,6 +16,8 @@ import (
 	"gorm.io/gen/field"
 
 	"gorm.io/plugin/dbresolver"
+
+	"github.com/0xJacky/Nginx-UI/model"
 )
 
 func newAuthToken(db *gorm.DB, opts ...gen.DOOption) authToken {
@@ -162,10 +163,6 @@ func (a authTokenDo) Where(conds ...gen.Condition) *authTokenDo {
 	return a.withDO(a.DO.Where(conds...))
 }
 
-func (a authTokenDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *authTokenDo {
-	return a.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
-}
-
 func (a authTokenDo) Order(conds ...field.Expr) *authTokenDo {
 	return a.withDO(a.DO.Order(conds...))
 }

+ 2 - 5
query/auths.gen.go

@@ -6,7 +6,6 @@ package query
 
 import (
 	"context"
-	"github.com/0xJacky/Nginx-UI/model"
 	"strings"
 
 	"gorm.io/gorm"
@@ -17,6 +16,8 @@ import (
 	"gorm.io/gen/field"
 
 	"gorm.io/plugin/dbresolver"
+
+	"github.com/0xJacky/Nginx-UI/model"
 )
 
 func newAuth(db *gorm.DB, opts ...gen.DOOption) auth {
@@ -182,10 +183,6 @@ func (a authDo) Where(conds ...gen.Condition) *authDo {
 	return a.withDO(a.DO.Where(conds...))
 }
 
-func (a authDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *authDo {
-	return a.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
-}
-
 func (a authDo) Order(conds ...field.Expr) *authDo {
 	return a.withDO(a.DO.Order(conds...))
 }

+ 31 - 34
query/certs.gen.go

@@ -6,7 +6,6 @@ package query
 
 import (
 	"context"
-	model2 "github.com/0xJacky/Nginx-UI/model"
 	"strings"
 
 	"gorm.io/gorm"
@@ -17,13 +16,15 @@ import (
 	"gorm.io/gen/field"
 
 	"gorm.io/plugin/dbresolver"
+
+	"github.com/0xJacky/Nginx-UI/model"
 )
 
 func newCert(db *gorm.DB, opts ...gen.DOOption) cert {
 	_cert := cert{}
 
 	_cert.certDo.UseDB(db, opts...)
-	_cert.certDo.UseModel(&model2.Cert{})
+	_cert.certDo.UseModel(&model.Cert{})
 
 	tableName := _cert.certDo.TableName()
 	_cert.ALL = field.NewAsterisk(tableName)
@@ -67,8 +68,8 @@ type cert struct {
 	AutoCert              field.Int
 	ChallengeMethod       field.String
 	DnsCredentialID       field.Int
-	Log           field.String
-	DnsCredential certBelongsToDnsCredential
+	Log                   field.String
+	DnsCredential         certBelongsToDnsCredential
 
 	fieldMap map[string]field.Expr
 }
@@ -170,17 +171,17 @@ func (a certBelongsToDnsCredential) Session(session *gorm.Session) *certBelongsT
 	return &a
 }
 
-func (a certBelongsToDnsCredential) Model(m *model2.Cert) *certBelongsToDnsCredentialTx {
+func (a certBelongsToDnsCredential) Model(m *model.Cert) *certBelongsToDnsCredentialTx {
 	return &certBelongsToDnsCredentialTx{a.db.Model(m).Association(a.Name())}
 }
 
 type certBelongsToDnsCredentialTx struct{ tx *gorm.Association }
 
-func (a certBelongsToDnsCredentialTx) Find() (result *model2.DnsCredential, err error) {
+func (a certBelongsToDnsCredentialTx) Find() (result *model.DnsCredential, err error) {
 	return result, a.tx.Find(&result)
 }
 
-func (a certBelongsToDnsCredentialTx) Append(values ...*model2.DnsCredential) (err error) {
+func (a certBelongsToDnsCredentialTx) Append(values ...*model.DnsCredential) (err error) {
 	targetValues := make([]interface{}, len(values))
 	for i, v := range values {
 		targetValues[i] = v
@@ -188,7 +189,7 @@ func (a certBelongsToDnsCredentialTx) Append(values ...*model2.DnsCredential) (e
 	return a.tx.Append(targetValues...)
 }
 
-func (a certBelongsToDnsCredentialTx) Replace(values ...*model2.DnsCredential) (err error) {
+func (a certBelongsToDnsCredentialTx) Replace(values ...*model.DnsCredential) (err error) {
 	targetValues := make([]interface{}, len(values))
 	for i, v := range values {
 		targetValues[i] = v
@@ -196,7 +197,7 @@ func (a certBelongsToDnsCredentialTx) Replace(values ...*model2.DnsCredential) (
 	return a.tx.Replace(targetValues...)
 }
 
-func (a certBelongsToDnsCredentialTx) Delete(values ...*model2.DnsCredential) (err error) {
+func (a certBelongsToDnsCredentialTx) Delete(values ...*model.DnsCredential) (err error) {
 	targetValues := make([]interface{}, len(values))
 	for i, v := range values {
 		targetValues[i] = v
@@ -215,7 +216,7 @@ func (a certBelongsToDnsCredentialTx) Count() int64 {
 type certDo struct{ gen.DO }
 
 // FirstByID Where("id=@id")
-func (c certDo) FirstByID(id int) (result *model2.Cert, err error) {
+func (c certDo) FirstByID(id int) (result *model.Cert, err error) {
 	var params []interface{}
 
 	var generateSQL strings.Builder
@@ -288,10 +289,6 @@ func (c certDo) Where(conds ...gen.Condition) *certDo {
 	return c.withDO(c.DO.Where(conds...))
 }
 
-func (c certDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *certDo {
-	return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
-}
-
 func (c certDo) Order(conds ...field.Expr) *certDo {
 	return c.withDO(c.DO.Order(conds...))
 }
@@ -340,57 +337,57 @@ func (c certDo) Unscoped() *certDo {
 	return c.withDO(c.DO.Unscoped())
 }
 
-func (c certDo) Create(values ...*model2.Cert) error {
+func (c certDo) Create(values ...*model.Cert) error {
 	if len(values) == 0 {
 		return nil
 	}
 	return c.DO.Create(values)
 }
 
-func (c certDo) CreateInBatches(values []*model2.Cert, batchSize int) error {
+func (c certDo) CreateInBatches(values []*model.Cert, batchSize int) error {
 	return c.DO.CreateInBatches(values, batchSize)
 }
 
 // Save : !!! underlying implementation is different with GORM
 // The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
-func (c certDo) Save(values ...*model2.Cert) error {
+func (c certDo) Save(values ...*model.Cert) error {
 	if len(values) == 0 {
 		return nil
 	}
 	return c.DO.Save(values)
 }
 
-func (c certDo) First() (*model2.Cert, error) {
+func (c certDo) First() (*model.Cert, error) {
 	if result, err := c.DO.First(); err != nil {
 		return nil, err
 	} else {
-		return result.(*model2.Cert), nil
+		return result.(*model.Cert), nil
 	}
 }
 
-func (c certDo) Take() (*model2.Cert, error) {
+func (c certDo) Take() (*model.Cert, error) {
 	if result, err := c.DO.Take(); err != nil {
 		return nil, err
 	} else {
-		return result.(*model2.Cert), nil
+		return result.(*model.Cert), nil
 	}
 }
 
-func (c certDo) Last() (*model2.Cert, error) {
+func (c certDo) Last() (*model.Cert, error) {
 	if result, err := c.DO.Last(); err != nil {
 		return nil, err
 	} else {
-		return result.(*model2.Cert), nil
+		return result.(*model.Cert), nil
 	}
 }
 
-func (c certDo) Find() ([]*model2.Cert, error) {
+func (c certDo) Find() ([]*model.Cert, error) {
 	result, err := c.DO.Find()
-	return result.([]*model2.Cert), err
+	return result.([]*model.Cert), err
 }
 
-func (c certDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model2.Cert, err error) {
-	buf := make([]*model2.Cert, 0, batchSize)
+func (c certDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Cert, err error) {
+	buf := make([]*model.Cert, 0, batchSize)
 	err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
 		defer func() { results = append(results, buf...) }()
 		return fc(tx, batch)
@@ -398,7 +395,7 @@ func (c certDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error)
 	return results, err
 }
 
-func (c certDo) FindInBatches(result *[]*model2.Cert, batchSize int, fc func(tx gen.Dao, batch int) error) error {
+func (c certDo) FindInBatches(result *[]*model.Cert, batchSize int, fc func(tx gen.Dao, batch int) error) error {
 	return c.DO.FindInBatches(result, batchSize, fc)
 }
 
@@ -424,23 +421,23 @@ func (c certDo) Preload(fields ...field.RelationField) *certDo {
 	return &c
 }
 
-func (c certDo) FirstOrInit() (*model2.Cert, error) {
+func (c certDo) FirstOrInit() (*model.Cert, error) {
 	if result, err := c.DO.FirstOrInit(); err != nil {
 		return nil, err
 	} else {
-		return result.(*model2.Cert), nil
+		return result.(*model.Cert), nil
 	}
 }
 
-func (c certDo) FirstOrCreate() (*model2.Cert, error) {
+func (c certDo) FirstOrCreate() (*model.Cert, error) {
 	if result, err := c.DO.FirstOrCreate(); err != nil {
 		return nil, err
 	} else {
-		return result.(*model2.Cert), nil
+		return result.(*model.Cert), nil
 	}
 }
 
-func (c certDo) FindByPage(offset int, limit int) (result []*model2.Cert, count int64, err error) {
+func (c certDo) FindByPage(offset int, limit int) (result []*model.Cert, count int64, err error) {
 	result, err = c.Offset(offset).Limit(limit).Find()
 	if err != nil {
 		return
@@ -469,7 +466,7 @@ func (c certDo) Scan(result interface{}) (err error) {
 	return c.DO.Scan(result)
 }
 
-func (c certDo) Delete(models ...*model2.Cert) (result gen.ResultInfo, err error) {
+func (c certDo) Delete(models ...*model.Cert) (result gen.ResultInfo, err error) {
 	return c.DO.Delete(models)
 }
 

+ 2 - 5
query/chat_gpt_logs.gen.go

@@ -6,7 +6,6 @@ package query
 
 import (
 	"context"
-	"github.com/0xJacky/Nginx-UI/model"
 	"strings"
 
 	"gorm.io/gorm"
@@ -17,6 +16,8 @@ import (
 	"gorm.io/gen/field"
 
 	"gorm.io/plugin/dbresolver"
+
+	"github.com/0xJacky/Nginx-UI/model"
 )
 
 func newChatGPTLog(db *gorm.DB, opts ...gen.DOOption) chatGPTLog {
@@ -166,10 +167,6 @@ func (c chatGPTLogDo) Where(conds ...gen.Condition) *chatGPTLogDo {
 	return c.withDO(c.DO.Where(conds...))
 }
 
-func (c chatGPTLogDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *chatGPTLogDo {
-	return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
-}
-
 func (c chatGPTLogDo) Order(conds ...field.Expr) *chatGPTLogDo {
 	return c.withDO(c.DO.Order(conds...))
 }

+ 2 - 5
query/config_backups.gen.go

@@ -6,7 +6,6 @@ package query
 
 import (
 	"context"
-	"github.com/0xJacky/Nginx-UI/model"
 	"strings"
 
 	"gorm.io/gorm"
@@ -17,6 +16,8 @@ import (
 	"gorm.io/gen/field"
 
 	"gorm.io/plugin/dbresolver"
+
+	"github.com/0xJacky/Nginx-UI/model"
 )
 
 func newConfigBackup(db *gorm.DB, opts ...gen.DOOption) configBackup {
@@ -186,10 +187,6 @@ func (c configBackupDo) Where(conds ...gen.Condition) *configBackupDo {
 	return c.withDO(c.DO.Where(conds...))
 }
 
-func (c configBackupDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *configBackupDo {
-	return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
-}
-
 func (c configBackupDo) Order(conds ...field.Expr) *configBackupDo {
 	return c.withDO(c.DO.Order(conds...))
 }

+ 2 - 5
query/dns_credentials.gen.go

@@ -6,7 +6,6 @@ package query
 
 import (
 	"context"
-	"github.com/0xJacky/Nginx-UI/model"
 	"strings"
 
 	"gorm.io/gorm"
@@ -17,6 +16,8 @@ import (
 	"gorm.io/gen/field"
 
 	"gorm.io/plugin/dbresolver"
+
+	"github.com/0xJacky/Nginx-UI/model"
 )
 
 func newDnsCredential(db *gorm.DB, opts ...gen.DOOption) dnsCredential {
@@ -186,10 +187,6 @@ func (d dnsCredentialDo) Where(conds ...gen.Condition) *dnsCredentialDo {
 	return d.withDO(d.DO.Where(conds...))
 }
 
-func (d dnsCredentialDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *dnsCredentialDo {
-	return d.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
-}
-
 func (d dnsCredentialDo) Order(conds ...field.Expr) *dnsCredentialDo {
 	return d.withDO(d.DO.Order(conds...))
 }

+ 2 - 5
query/environments.gen.go

@@ -6,7 +6,6 @@ package query
 
 import (
 	"context"
-	"github.com/0xJacky/Nginx-UI/model"
 	"strings"
 
 	"gorm.io/gorm"
@@ -17,6 +16,8 @@ import (
 	"gorm.io/gen/field"
 
 	"gorm.io/plugin/dbresolver"
+
+	"github.com/0xJacky/Nginx-UI/model"
 )
 
 func newEnvironment(db *gorm.DB, opts ...gen.DOOption) environment {
@@ -194,10 +195,6 @@ func (e environmentDo) Where(conds ...gen.Condition) *environmentDo {
 	return e.withDO(e.DO.Where(conds...))
 }
 
-func (e environmentDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *environmentDo {
-	return e.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
-}
-
 func (e environmentDo) Order(conds ...field.Expr) *environmentDo {
 	return e.withDO(e.DO.Order(conds...))
 }

+ 8 - 0
query/gen.go

@@ -24,6 +24,7 @@ var (
 	ConfigBackup  *configBackup
 	DnsCredential *dnsCredential
 	Environment   *environment
+	Notification  *notification
 	Site          *site
 )
 
@@ -36,6 +37,7 @@ func SetDefault(db *gorm.DB, opts ...gen.DOOption) {
 	ConfigBackup = &Q.ConfigBackup
 	DnsCredential = &Q.DnsCredential
 	Environment = &Q.Environment
+	Notification = &Q.Notification
 	Site = &Q.Site
 }
 
@@ -49,6 +51,7 @@ func Use(db *gorm.DB, opts ...gen.DOOption) *Query {
 		ConfigBackup:  newConfigBackup(db, opts...),
 		DnsCredential: newDnsCredential(db, opts...),
 		Environment:   newEnvironment(db, opts...),
+		Notification:  newNotification(db, opts...),
 		Site:          newSite(db, opts...),
 	}
 }
@@ -63,6 +66,7 @@ type Query struct {
 	ConfigBackup  configBackup
 	DnsCredential dnsCredential
 	Environment   environment
+	Notification  notification
 	Site          site
 }
 
@@ -78,6 +82,7 @@ func (q *Query) clone(db *gorm.DB) *Query {
 		ConfigBackup:  q.ConfigBackup.clone(db),
 		DnsCredential: q.DnsCredential.clone(db),
 		Environment:   q.Environment.clone(db),
+		Notification:  q.Notification.clone(db),
 		Site:          q.Site.clone(db),
 	}
 }
@@ -100,6 +105,7 @@ func (q *Query) ReplaceDB(db *gorm.DB) *Query {
 		ConfigBackup:  q.ConfigBackup.replaceDB(db),
 		DnsCredential: q.DnsCredential.replaceDB(db),
 		Environment:   q.Environment.replaceDB(db),
+		Notification:  q.Notification.replaceDB(db),
 		Site:          q.Site.replaceDB(db),
 	}
 }
@@ -112,6 +118,7 @@ type queryCtx struct {
 	ConfigBackup  *configBackupDo
 	DnsCredential *dnsCredentialDo
 	Environment   *environmentDo
+	Notification  *notificationDo
 	Site          *siteDo
 }
 
@@ -124,6 +131,7 @@ func (q *Query) WithContext(ctx context.Context) *queryCtx {
 		ConfigBackup:  q.ConfigBackup.WithContext(ctx),
 		DnsCredential: q.DnsCredential.WithContext(ctx),
 		Environment:   q.Environment.WithContext(ctx),
+		Notification:  q.Notification.WithContext(ctx),
 		Site:          q.Site.WithContext(ctx),
 	}
 }

+ 374 - 0
query/notifications.gen.go

@@ -0,0 +1,374 @@
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+// Code generated by gorm.io/gen. DO NOT EDIT.
+
+package query
+
+import (
+	"context"
+	"strings"
+
+	"gorm.io/gorm"
+	"gorm.io/gorm/clause"
+	"gorm.io/gorm/schema"
+
+	"gorm.io/gen"
+	"gorm.io/gen/field"
+
+	"gorm.io/plugin/dbresolver"
+
+	"github.com/0xJacky/Nginx-UI/model"
+)
+
+func newNotification(db *gorm.DB, opts ...gen.DOOption) notification {
+	_notification := notification{}
+
+	_notification.notificationDo.UseDB(db, opts...)
+	_notification.notificationDo.UseModel(&model.Notification{})
+
+	tableName := _notification.notificationDo.TableName()
+	_notification.ALL = field.NewAsterisk(tableName)
+	_notification.ID = field.NewInt(tableName, "id")
+	_notification.CreatedAt = field.NewTime(tableName, "created_at")
+	_notification.UpdatedAt = field.NewTime(tableName, "updated_at")
+	_notification.DeletedAt = field.NewField(tableName, "deleted_at")
+	_notification.Type = field.NewInt(tableName, "type")
+	_notification.Title = field.NewString(tableName, "title")
+	_notification.Details = field.NewString(tableName, "details")
+
+	_notification.fillFieldMap()
+
+	return _notification
+}
+
+type notification struct {
+	notificationDo
+
+	ALL       field.Asterisk
+	ID        field.Int
+	CreatedAt field.Time
+	UpdatedAt field.Time
+	DeletedAt field.Field
+	Type      field.Int
+	Title     field.String
+	Details   field.String
+
+	fieldMap map[string]field.Expr
+}
+
+func (n notification) Table(newTableName string) *notification {
+	n.notificationDo.UseTable(newTableName)
+	return n.updateTableName(newTableName)
+}
+
+func (n notification) As(alias string) *notification {
+	n.notificationDo.DO = *(n.notificationDo.As(alias).(*gen.DO))
+	return n.updateTableName(alias)
+}
+
+func (n *notification) updateTableName(table string) *notification {
+	n.ALL = field.NewAsterisk(table)
+	n.ID = field.NewInt(table, "id")
+	n.CreatedAt = field.NewTime(table, "created_at")
+	n.UpdatedAt = field.NewTime(table, "updated_at")
+	n.DeletedAt = field.NewField(table, "deleted_at")
+	n.Type = field.NewInt(table, "type")
+	n.Title = field.NewString(table, "title")
+	n.Details = field.NewString(table, "details")
+
+	n.fillFieldMap()
+
+	return n
+}
+
+func (n *notification) GetFieldByName(fieldName string) (field.OrderExpr, bool) {
+	_f, ok := n.fieldMap[fieldName]
+	if !ok || _f == nil {
+		return nil, false
+	}
+	_oe, ok := _f.(field.OrderExpr)
+	return _oe, ok
+}
+
+func (n *notification) fillFieldMap() {
+	n.fieldMap = make(map[string]field.Expr, 7)
+	n.fieldMap["id"] = n.ID
+	n.fieldMap["created_at"] = n.CreatedAt
+	n.fieldMap["updated_at"] = n.UpdatedAt
+	n.fieldMap["deleted_at"] = n.DeletedAt
+	n.fieldMap["type"] = n.Type
+	n.fieldMap["title"] = n.Title
+	n.fieldMap["details"] = n.Details
+}
+
+func (n notification) clone(db *gorm.DB) notification {
+	n.notificationDo.ReplaceConnPool(db.Statement.ConnPool)
+	return n
+}
+
+func (n notification) replaceDB(db *gorm.DB) notification {
+	n.notificationDo.ReplaceDB(db)
+	return n
+}
+
+type notificationDo struct{ gen.DO }
+
+// FirstByID Where("id=@id")
+func (n notificationDo) FirstByID(id int) (result *model.Notification, err error) {
+	var params []interface{}
+
+	var generateSQL strings.Builder
+	params = append(params, id)
+	generateSQL.WriteString("id=? ")
+
+	var executeSQL *gorm.DB
+	executeSQL = n.UnderlyingDB().Where(generateSQL.String(), params...).Take(&result) // ignore_security_alert
+	err = executeSQL.Error
+
+	return
+}
+
+// DeleteByID update @@table set deleted_at=strftime('%Y-%m-%d %H:%M:%S','now') where id=@id
+func (n notificationDo) DeleteByID(id int) (err error) {
+	var params []interface{}
+
+	var generateSQL strings.Builder
+	params = append(params, id)
+	generateSQL.WriteString("update notifications set deleted_at=strftime('%Y-%m-%d %H:%M:%S','now') where id=? ")
+
+	var executeSQL *gorm.DB
+	executeSQL = n.UnderlyingDB().Exec(generateSQL.String(), params...) // ignore_security_alert
+	err = executeSQL.Error
+
+	return
+}
+
+func (n notificationDo) Debug() *notificationDo {
+	return n.withDO(n.DO.Debug())
+}
+
+func (n notificationDo) WithContext(ctx context.Context) *notificationDo {
+	return n.withDO(n.DO.WithContext(ctx))
+}
+
+func (n notificationDo) ReadDB() *notificationDo {
+	return n.Clauses(dbresolver.Read)
+}
+
+func (n notificationDo) WriteDB() *notificationDo {
+	return n.Clauses(dbresolver.Write)
+}
+
+func (n notificationDo) Session(config *gorm.Session) *notificationDo {
+	return n.withDO(n.DO.Session(config))
+}
+
+func (n notificationDo) Clauses(conds ...clause.Expression) *notificationDo {
+	return n.withDO(n.DO.Clauses(conds...))
+}
+
+func (n notificationDo) Returning(value interface{}, columns ...string) *notificationDo {
+	return n.withDO(n.DO.Returning(value, columns...))
+}
+
+func (n notificationDo) Not(conds ...gen.Condition) *notificationDo {
+	return n.withDO(n.DO.Not(conds...))
+}
+
+func (n notificationDo) Or(conds ...gen.Condition) *notificationDo {
+	return n.withDO(n.DO.Or(conds...))
+}
+
+func (n notificationDo) Select(conds ...field.Expr) *notificationDo {
+	return n.withDO(n.DO.Select(conds...))
+}
+
+func (n notificationDo) Where(conds ...gen.Condition) *notificationDo {
+	return n.withDO(n.DO.Where(conds...))
+}
+
+func (n notificationDo) Order(conds ...field.Expr) *notificationDo {
+	return n.withDO(n.DO.Order(conds...))
+}
+
+func (n notificationDo) Distinct(cols ...field.Expr) *notificationDo {
+	return n.withDO(n.DO.Distinct(cols...))
+}
+
+func (n notificationDo) Omit(cols ...field.Expr) *notificationDo {
+	return n.withDO(n.DO.Omit(cols...))
+}
+
+func (n notificationDo) Join(table schema.Tabler, on ...field.Expr) *notificationDo {
+	return n.withDO(n.DO.Join(table, on...))
+}
+
+func (n notificationDo) LeftJoin(table schema.Tabler, on ...field.Expr) *notificationDo {
+	return n.withDO(n.DO.LeftJoin(table, on...))
+}
+
+func (n notificationDo) RightJoin(table schema.Tabler, on ...field.Expr) *notificationDo {
+	return n.withDO(n.DO.RightJoin(table, on...))
+}
+
+func (n notificationDo) Group(cols ...field.Expr) *notificationDo {
+	return n.withDO(n.DO.Group(cols...))
+}
+
+func (n notificationDo) Having(conds ...gen.Condition) *notificationDo {
+	return n.withDO(n.DO.Having(conds...))
+}
+
+func (n notificationDo) Limit(limit int) *notificationDo {
+	return n.withDO(n.DO.Limit(limit))
+}
+
+func (n notificationDo) Offset(offset int) *notificationDo {
+	return n.withDO(n.DO.Offset(offset))
+}
+
+func (n notificationDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *notificationDo {
+	return n.withDO(n.DO.Scopes(funcs...))
+}
+
+func (n notificationDo) Unscoped() *notificationDo {
+	return n.withDO(n.DO.Unscoped())
+}
+
+func (n notificationDo) Create(values ...*model.Notification) error {
+	if len(values) == 0 {
+		return nil
+	}
+	return n.DO.Create(values)
+}
+
+func (n notificationDo) CreateInBatches(values []*model.Notification, batchSize int) error {
+	return n.DO.CreateInBatches(values, batchSize)
+}
+
+// Save : !!! underlying implementation is different with GORM
+// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values)
+func (n notificationDo) Save(values ...*model.Notification) error {
+	if len(values) == 0 {
+		return nil
+	}
+	return n.DO.Save(values)
+}
+
+func (n notificationDo) First() (*model.Notification, error) {
+	if result, err := n.DO.First(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.Notification), nil
+	}
+}
+
+func (n notificationDo) Take() (*model.Notification, error) {
+	if result, err := n.DO.Take(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.Notification), nil
+	}
+}
+
+func (n notificationDo) Last() (*model.Notification, error) {
+	if result, err := n.DO.Last(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.Notification), nil
+	}
+}
+
+func (n notificationDo) Find() ([]*model.Notification, error) {
+	result, err := n.DO.Find()
+	return result.([]*model.Notification), err
+}
+
+func (n notificationDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Notification, err error) {
+	buf := make([]*model.Notification, 0, batchSize)
+	err = n.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error {
+		defer func() { results = append(results, buf...) }()
+		return fc(tx, batch)
+	})
+	return results, err
+}
+
+func (n notificationDo) FindInBatches(result *[]*model.Notification, batchSize int, fc func(tx gen.Dao, batch int) error) error {
+	return n.DO.FindInBatches(result, batchSize, fc)
+}
+
+func (n notificationDo) Attrs(attrs ...field.AssignExpr) *notificationDo {
+	return n.withDO(n.DO.Attrs(attrs...))
+}
+
+func (n notificationDo) Assign(attrs ...field.AssignExpr) *notificationDo {
+	return n.withDO(n.DO.Assign(attrs...))
+}
+
+func (n notificationDo) Joins(fields ...field.RelationField) *notificationDo {
+	for _, _f := range fields {
+		n = *n.withDO(n.DO.Joins(_f))
+	}
+	return &n
+}
+
+func (n notificationDo) Preload(fields ...field.RelationField) *notificationDo {
+	for _, _f := range fields {
+		n = *n.withDO(n.DO.Preload(_f))
+	}
+	return &n
+}
+
+func (n notificationDo) FirstOrInit() (*model.Notification, error) {
+	if result, err := n.DO.FirstOrInit(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.Notification), nil
+	}
+}
+
+func (n notificationDo) FirstOrCreate() (*model.Notification, error) {
+	if result, err := n.DO.FirstOrCreate(); err != nil {
+		return nil, err
+	} else {
+		return result.(*model.Notification), nil
+	}
+}
+
+func (n notificationDo) FindByPage(offset int, limit int) (result []*model.Notification, count int64, err error) {
+	result, err = n.Offset(offset).Limit(limit).Find()
+	if err != nil {
+		return
+	}
+
+	if size := len(result); 0 < limit && 0 < size && size < limit {
+		count = int64(size + offset)
+		return
+	}
+
+	count, err = n.Offset(-1).Limit(-1).Count()
+	return
+}
+
+func (n notificationDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) {
+	count, err = n.Count()
+	if err != nil {
+		return
+	}
+
+	err = n.Offset(offset).Limit(limit).Scan(result)
+	return
+}
+
+func (n notificationDo) Scan(result interface{}) (err error) {
+	return n.DO.Scan(result)
+}
+
+func (n notificationDo) Delete(models ...*model.Notification) (result gen.ResultInfo, err error) {
+	return n.DO.Delete(models)
+}
+
+func (n *notificationDo) withDO(do gen.Dao) *notificationDo {
+	n.DO = *do.(*gen.DO)
+	return n
+}

+ 2 - 5
query/sites.gen.go

@@ -6,7 +6,6 @@ package query
 
 import (
 	"context"
-	"github.com/0xJacky/Nginx-UI/model"
 	"strings"
 
 	"gorm.io/gorm"
@@ -17,6 +16,8 @@ import (
 	"gorm.io/gen/field"
 
 	"gorm.io/plugin/dbresolver"
+
+	"github.com/0xJacky/Nginx-UI/model"
 )
 
 func newSite(db *gorm.DB, opts ...gen.DOOption) site {
@@ -182,10 +183,6 @@ func (s siteDo) Where(conds ...gen.Condition) *siteDo {
 	return s.withDO(s.DO.Where(conds...))
 }
 
-func (s siteDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *siteDo {
-	return s.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB()))
-}
-
 func (s siteDo) Order(conds ...field.Expr) *siteDo {
 	return s.withDO(s.DO.Order(conds...))
 }

BIN
resources/demo/demo.db


+ 2 - 0
router/routers.go

@@ -6,6 +6,7 @@ import (
 	"github.com/0xJacky/Nginx-UI/api/cluster"
 	"github.com/0xJacky/Nginx-UI/api/config"
 	"github.com/0xJacky/Nginx-UI/api/nginx"
+	"github.com/0xJacky/Nginx-UI/api/notification"
 	"github.com/0xJacky/Nginx-UI/api/openai"
 	"github.com/0xJacky/Nginx-UI/api/sites"
 	"github.com/0xJacky/Nginx-UI/api/system"
@@ -54,6 +55,7 @@ func InitRouter() *gin.Engine {
 			system.InitPrivateRouter(g)
 			openai.InitRouter(g)
 			cluster.InitRouter(g)
+			notification.InitRouter(g)
 		}
 
 		// Authorization required and websocket request