Browse Source

fix: resolved all vue-tsc errors

0xJacky 1 year ago
parent
commit
ab1adcfa3d
64 changed files with 673 additions and 449 deletions
  1. 33 101
      api/analytic/analytic.go
  2. 70 0
      api/analytic/nodes.go
  3. 52 0
      api/analytic/type.go
  4. 5 5
      api/certificate/dns_credential.go
  5. 112 1
      app/src/api/analytic.ts
  6. 1 1
      app/src/api/auth.ts
  7. 7 2
      app/src/api/auto_cert.ts
  8. 7 0
      app/src/api/environment.ts
  9. 3 3
      app/src/api/template.ts
  10. 9 0
      app/src/api/upgrade.ts
  11. 1 1
      app/src/components/Chart/RadialBarChart.vue
  12. 3 1
      app/src/components/Chart/types.d.ts
  13. 6 4
      app/src/components/NodeSelector/NodeSelector.vue
  14. 3 6
      app/src/components/PageHeader/PageHeader.vue
  15. 1 1
      app/src/components/SetLanguage/SetLanguage.vue
  16. 8 6
      app/src/components/StdDesign/StdDataDisplay/StdCurd.vue
  17. 22 18
      app/src/components/StdDesign/StdDataDisplay/StdTable.vue
  18. 4 4
      app/src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts
  19. 2 2
      app/src/components/StdDesign/StdDataEntry/StdDataEntry.vue
  20. 2 2
      app/src/components/StdDesign/StdDataEntry/StdFormItem.vue
  21. 2 2
      app/src/components/StdDesign/StdDataEntry/components/StdPassword.vue
  22. 8 10
      app/src/components/StdDesign/StdDataEntry/components/StdSelector.vue
  23. 6 3
      app/src/components/StdDesign/StdDataEntry/index.tsx
  24. 4 6
      app/src/components/StdDesign/types.d.ts
  25. 1 1
      app/src/layouts/HeaderLayout.vue
  26. 5 5
      app/src/layouts/SideBar.vue
  27. 1 1
      app/src/routes/index.ts
  28. 1 1
      app/src/version.json
  29. 10 10
      app/src/views/cert/Cert.vue
  30. 13 6
      app/src/views/cert/DNSChallenge.vue
  31. 5 5
      app/src/views/cert/DNSCredential.vue
  32. 4 3
      app/src/views/config/ConfigEdit.vue
  33. 13 16
      app/src/views/dashboard/Environments.vue
  34. 38 33
      app/src/views/dashboard/ServerAnalytic.vue
  35. 4 4
      app/src/views/domain/DomainAdd.vue
  36. 11 11
      app/src/views/domain/DomainEdit.vue
  37. 8 7
      app/src/views/domain/DomainList.vue
  38. 1 1
      app/src/views/domain/cert/CertInfo.vue
  39. 14 11
      app/src/views/domain/cert/ChangeCert.vue
  40. 8 5
      app/src/views/domain/cert/IssueCert.vue
  41. 18 21
      app/src/views/domain/cert/components/AutoCertStepOne.vue
  42. 11 5
      app/src/views/domain/cert/components/DNSChallenge.vue
  43. 30 28
      app/src/views/domain/cert/components/ObtainCert.vue
  44. 1 1
      app/src/views/domain/components/Deploy.vue
  45. 7 5
      app/src/views/domain/components/RightSettings.vue
  46. 7 3
      app/src/views/domain/components/SiteDuplicate.vue
  47. 2 2
      app/src/views/domain/ngx_conf/LocationEditor.vue
  48. 2 2
      app/src/views/domain/ngx_conf/LogEntry.vue
  49. 13 12
      app/src/views/domain/ngx_conf/NgxConfigEditor.vue
  50. 2 2
      app/src/views/domain/ngx_conf/config_template/ConfigTemplate.vue
  51. 3 3
      app/src/views/domain/ngx_conf/config_template/TemplateForm.vue
  52. 1 1
      app/src/views/domain/ngx_conf/config_template/TemplateFormItem.vue
  53. 2 1
      app/src/views/domain/ngx_conf/directive/DirectiveAdd.vue
  54. 2 2
      app/src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue
  55. 11 8
      app/src/views/environment/Environment.vue
  56. 4 8
      app/src/views/nginx_log/NginxLog.vue
  57. 12 1
      app/src/views/other/Error.vue
  58. 1 1
      app/src/views/other/Login.vue
  59. 3 3
      app/src/views/pty/Terminal.vue
  60. 8 17
      app/src/views/system/Upgrade.vue
  61. 1 1
      app/version.json
  62. 13 12
      internal/analytic/analytic.go
  63. 6 6
      internal/analytic/record.go
  64. 5 5
      internal/analytic/stat.go

+ 33 - 101
api/analytic/analytic.go

@@ -2,7 +2,7 @@ package analytic
 
 import (
 	"fmt"
-	analytic2 "github.com/0xJacky/Nginx-UI/internal/analytic"
+	"github.com/0xJacky/Nginx-UI/internal/analytic"
 	"github.com/0xJacky/Nginx-UI/internal/logger"
 	"github.com/shirou/gopsutil/v3/cpu"
 	"github.com/shirou/gopsutil/v3/host"
@@ -17,22 +17,6 @@ import (
 	"github.com/gorilla/websocket"
 )
 
-type CPUStat struct {
-	User   float64 `json:"user"`
-	System float64 `json:"system"`
-	Idle   float64 `json:"idle"`
-	Total  float64 `json:"total"`
-}
-
-type Stat struct {
-	Uptime  uint64             `json:"uptime"`
-	LoadAvg *load.AvgStat     `json:"loadavg"`
-	CPU     CPUStat           `json:"cpu"`
-	Memory  analytic2.MemStat `json:"memory"`
-	Disk    analytic2.DiskStat `json:"disk"`
-	Network net.IOCountersStat `json:"network"`
-}
-
 func Analytic(c *gin.Context) {
 	var upGrader = websocket.Upgrader{
 		CheckOrigin: func(r *http.Request) bool {
@@ -51,7 +35,7 @@ func Analytic(c *gin.Context) {
 	var stat Stat
 
 	for {
-		stat.Memory, err = analytic2.GetMemoryStat()
+		stat.Memory, err = analytic.GetMemoryStat()
 
 		if err != nil {
 			logger.Error(err)
@@ -76,7 +60,7 @@ func Analytic(c *gin.Context) {
 
 		stat.LoadAvg, _ = load.Avg()
 
-		stat.Disk, err = analytic2.GetDiskStat()
+		stat.Disk, err = analytic.GetDiskStat()
 
 		if err != nil {
 			logger.Error(err)
@@ -103,20 +87,24 @@ func Analytic(c *gin.Context) {
 }
 
 func GetAnalyticInit(c *gin.Context) {
-	cpuInfo, _ := cpu.Info()
-	network, _ := net.IOCounters(false)
-	memory, err := analytic2.GetMemoryStat()
+	cpuInfo, err := cpu.Info()
+	if err != nil {
+		logger.Error(err)
+	}
 
+	network, err := net.IOCounters(false)
 	if err != nil {
 		logger.Error(err)
-		return
 	}
 
-	diskStat, err := analytic2.GetDiskStat()
+	memory, err := analytic.GetMemoryStat()
+	if err != nil {
+		logger.Error(err)
+	}
 
+	diskStat, err := analytic.GetDiskStat()
 	if err != nil {
 		logger.Error(err)
-		return
 	}
 
 	var _net net.IOCountersStat
@@ -132,86 +120,30 @@ func GetAnalyticInit(c *gin.Context) {
 		hostInfo.Platform = "CentOS"
 	}
 
-	loadAvg, _ := load.Avg()
-
-	c.JSON(http.StatusOK, gin.H{
-		"host": hostInfo,
-		"cpu": gin.H{
-			"info":  cpuInfo,
-			"user":  analytic2.CpuUserRecord,
-			"total": analytic2.CpuTotalRecord,
-		},
-		"network": gin.H{
-			"init":      _net,
-			"bytesRecv": analytic2.NetRecvRecord,
-			"bytesSent": analytic2.NetSentRecord,
-		},
-		"disk_io": gin.H{
-			"writes": analytic2.DiskWriteRecord,
-			"reads":  analytic2.DiskReadRecord,
-		},
-		"memory":  memory,
-		"disk":    diskStat,
-		"loadavg": loadAvg,
-	})
-}
+	loadAvg, err := load.Avg()
 
-func GetNodeStat(c *gin.Context) {
-	var upGrader = websocket.Upgrader{
-		CheckOrigin: func(r *http.Request) bool {
-			return true
-		},
-	}
-	// upgrade http to websocket
-	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
 	if err != nil {
 		logger.Error(err)
-		return
-	}
-
-	defer ws.Close()
-
-	for {
-		// write
-		err = ws.WriteJSON(analytic2.GetNodeStat())
-		if err != nil || websocket.IsUnexpectedCloseError(err,
-			websocket.CloseGoingAway,
-			websocket.CloseNoStatusReceived,
-			websocket.CloseNormalClosure) {
-			logger.Error(err)
-			break
-		}
-
-		time.Sleep(10 * time.Second)
 	}
-}
 
-func GetNodesAnalytic(c *gin.Context) {
-	var upGrader = websocket.Upgrader{
-		CheckOrigin: func(r *http.Request) bool {
-			return true
+	c.JSON(http.StatusOK, InitResp{
+		Host: hostInfo,
+		CPU: CPURecords{
+			Info:  cpuInfo,
+			User:  analytic.CpuUserRecord,
+			Total: analytic.CpuTotalRecord,
 		},
-	}
-	// upgrade http to websocket
-	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
-	if err != nil {
-		logger.Error(err)
-		return
-	}
-
-	defer ws.Close()
-
-	for {
-		// write
-		err = ws.WriteJSON(analytic2.NodeMap)
-		if err != nil || websocket.IsUnexpectedCloseError(err,
-			websocket.CloseGoingAway,
-			websocket.CloseNoStatusReceived,
-			websocket.CloseNormalClosure) {
-			logger.Error(err)
-			break
-		}
-
-		time.Sleep(10 * time.Second)
-	}
+		Network: NetworkRecords{
+			Init:      _net,
+			BytesRecv: analytic.NetRecvRecord,
+			BytesSent: analytic.NetSentRecord,
+		},
+		DiskIO: DiskIORecords{
+			Writes: analytic.DiskWriteRecord,
+			Reads:  analytic.DiskReadRecord,
+		},
+		Memory:  memory,
+		Disk:    diskStat,
+		LoadAvg: loadAvg,
+	})
 }

+ 70 - 0
api/analytic/nodes.go

@@ -0,0 +1,70 @@
+package analytic
+
+import (
+	"github.com/0xJacky/Nginx-UI/internal/analytic"
+	"github.com/0xJacky/Nginx-UI/internal/logger"
+	"github.com/gin-gonic/gin"
+	"github.com/gorilla/websocket"
+	"net/http"
+	"time"
+)
+
+func GetNodeStat(c *gin.Context) {
+	var upGrader = websocket.Upgrader{
+		CheckOrigin: func(r *http.Request) bool {
+			return true
+		},
+	}
+	// upgrade http to websocket
+	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
+	if err != nil {
+		logger.Error(err)
+		return
+	}
+
+	defer ws.Close()
+
+	for {
+		// write
+		err = ws.WriteJSON(analytic.GetNodeStat())
+		if err != nil || websocket.IsUnexpectedCloseError(err,
+			websocket.CloseGoingAway,
+			websocket.CloseNoStatusReceived,
+			websocket.CloseNormalClosure) {
+			logger.Error(err)
+			break
+		}
+
+		time.Sleep(10 * time.Second)
+	}
+}
+
+func GetNodesAnalytic(c *gin.Context) {
+	var upGrader = websocket.Upgrader{
+		CheckOrigin: func(r *http.Request) bool {
+			return true
+		},
+	}
+	// upgrade http to websocket
+	ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)
+	if err != nil {
+		logger.Error(err)
+		return
+	}
+
+	defer ws.Close()
+
+	for {
+		// write
+		err = ws.WriteJSON(analytic.NodeMap)
+		if err != nil || websocket.IsUnexpectedCloseError(err,
+			websocket.CloseGoingAway,
+			websocket.CloseNoStatusReceived,
+			websocket.CloseNormalClosure) {
+			logger.Error(err)
+			break
+		}
+
+		time.Sleep(10 * time.Second)
+	}
+}

+ 52 - 0
api/analytic/type.go

@@ -0,0 +1,52 @@
+package analytic
+
+import (
+	"github.com/0xJacky/Nginx-UI/internal/analytic"
+	"github.com/shirou/gopsutil/v3/cpu"
+	"github.com/shirou/gopsutil/v3/host"
+	"github.com/shirou/gopsutil/v3/load"
+	"github.com/shirou/gopsutil/v3/net"
+)
+
+type CPUStat struct {
+	User   float64 `json:"user"`
+	System float64 `json:"system"`
+	Idle   float64 `json:"idle"`
+	Total  float64 `json:"total"`
+}
+
+type Stat struct {
+	Uptime  uint64             `json:"uptime"`
+	LoadAvg *load.AvgStat      `json:"loadavg"`
+	CPU     CPUStat            `json:"cpu"`
+	Memory  analytic.MemStat   `json:"memory"`
+	Disk    analytic.DiskStat  `json:"disk"`
+	Network net.IOCountersStat `json:"network"`
+}
+
+type CPURecords struct {
+	Info  []cpu.InfoStat            `json:"info"`
+	User  []analytic.Usage[float64] `json:"user"`
+	Total []analytic.Usage[float64] `json:"total"`
+}
+
+type NetworkRecords struct {
+	Init      net.IOCountersStat       `json:"init"`
+	BytesRecv []analytic.Usage[uint64] `json:"bytesRecv"`
+	BytesSent []analytic.Usage[uint64] `json:"bytesSent"`
+}
+
+type DiskIORecords struct {
+	Writes []analytic.Usage[uint64] `json:"writes"`
+	Reads  []analytic.Usage[uint64] `json:"reads"`
+}
+
+type InitResp struct {
+	Host    *host.InfoStat    `json:"host"`
+	CPU     CPURecords        `json:"cpu"`
+	Network NetworkRecords    `json:"network"`
+	DiskIO  DiskIORecords     `json:"disk_io"`
+	Memory  analytic.MemStat  `json:"memory"`
+	Disk    analytic.DiskStat `json:"disk"`
+	LoadAvg *load.AvgStat     `json:"loadavg"`
+}

+ 5 - 5
api/certificate/dns_credential.go

@@ -3,7 +3,7 @@ package certificate
 import (
 	"github.com/0xJacky/Nginx-UI/api"
 	"github.com/0xJacky/Nginx-UI/internal/cert/dns"
-	model2 "github.com/0xJacky/Nginx-UI/model"
+	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/0xJacky/Nginx-UI/query"
 	"github.com/gin-gonic/gin"
 	"github.com/spf13/cast"
@@ -21,7 +21,7 @@ func GetDnsCredential(c *gin.Context) {
 		return
 	}
 	type apiDnsCredential struct {
-		model2.Model
+		model.Model
 		Name string `json:"name"`
 		dns.Config
 	}
@@ -35,7 +35,7 @@ func GetDnsCredential(c *gin.Context) {
 func GetDnsCredentialList(c *gin.Context) {
 	d := query.DnsCredential
 	provider := c.Query("provider")
-	var data []*model2.DnsCredential
+	var data []*model.DnsCredential
 	var err error
 	if provider != "" {
 		data, err = d.Where(d.Provider.Eq(provider)).Find()
@@ -65,7 +65,7 @@ func AddDnsCredential(c *gin.Context) {
 	}
 
 	json.Config.Name = json.Provider
-	dnsCredential := model2.DnsCredential{
+	dnsCredential := model.DnsCredential{
 		Name:     json.Name,
 		Config:   &json.Config,
 		Provider: json.Provider,
@@ -99,7 +99,7 @@ func EditDnsCredential(c *gin.Context) {
 	}
 
 	json.Config.Name = json.Provider
-	_, err = d.Where(d.ID.Eq(dnsCredential.ID)).Updates(&model2.DnsCredential{
+	_, err = d.Where(d.ID.Eq(dnsCredential.ID)).Updates(&model.DnsCredential{
 		Name:     json.Name,
 		Config:   &json.Config,
 		Provider: json.Provider,

+ 112 - 1
app/src/api/analytic.ts

@@ -1,9 +1,120 @@
 import http from '@/lib/http'
+import ws from '@/lib/websocket'
+
+export interface CPUInfoStat {
+  cpu: number
+  vendorId: string
+  family: string
+  model: string
+  stepping: number
+  physicalId: string
+  coreId: string
+  cores: number
+  modelName: string
+  mhz: number
+  cacheSize: number
+  flags: string[]
+  microcode: string
+}
+
+export interface IOCountersStat {
+  name: string
+  bytesSent: number
+  bytesRecv: number
+  packetsSent: number
+  packetsRecv: number
+  errin: number
+  errout: number
+  dropin: number
+  dropout: number
+  fifoin: number
+  fifoout: number
+}
+
+export interface HostInfoStat {
+  hostname: string
+  uptime: number
+  bootTime: number
+  procs: number
+  os: string
+  platform: string
+  platformFamily: string
+  platformVersion: string
+  kernelVersion: string
+  kernelArch: string
+  virtualizationSystem: string
+  virtualizationRole: string
+  hostId: string
+}
+
+export interface MemStat {
+  total: string
+  used: string
+  cached: string
+  free: string
+  swap_used: string
+  swap_total: string
+  swap_cached: string
+  swap_percent: number
+  pressure: number
+}
+
+export interface DiskStat {
+  total: string
+  used: string
+  percentage: number
+  writes: Usage
+  reads: Usage
+}
+
+export interface LoadStat {
+  load1: number
+  load5: number
+  load15: number
+}
+
+export interface Usage {
+  x: string
+  y: number
+}
+
+export interface CPURecords {
+  info: CPUInfoStat[]
+  user: Usage[]
+  total: Usage[]
+}
+
+export interface NetworkRecords {
+  init: IOCountersStat
+  bytesRecv: Usage[]
+  bytesSent: Usage[]
+}
+
+export interface DiskIORecords {
+  writes: Usage[]
+  reads: Usage[]
+}
+
+export interface AnalyticInit {
+  host: HostInfoStat
+  cpu: CPURecords
+  network: NetworkRecords
+  disk_io: DiskIORecords
+  disk: DiskStat
+  memory: MemStat
+  loadavg: LoadStat
+}
 
 const analytic = {
-  init() {
+  init(): Promise<AnalyticInit> {
     return http.get('/analytic/init')
   },
+  server() {
+    return ws('/api/analytic')
+  },
+  nodes() {
+    return ws('/api/analytic/nodes')
+  },
 }
 
 export default analytic

+ 1 - 1
app/src/api/auth.ts

@@ -16,7 +16,7 @@ const auth = {
       login(r.token)
     })
   },
-  async casdoor_login(code: string, state: string) {
+  async casdoor_login(code?: string, state?: string) {
     await http.post('/casdoor_callback', {
       code,
       state,

+ 7 - 2
app/src/api/auto_cert.ts

@@ -1,8 +1,9 @@
 import http from '@/lib/http'
 
 export interface DNSProvider {
-  name: string
+  name?: string
   code: string
+  provider?: string
   configuration: {
     credentials: {
       [key: string]: string
@@ -11,11 +12,15 @@ export interface DNSProvider {
       [key: string]: string
     }
   }
-  links: {
+  links?: {
     api: string
     go_client: string
   }
 }
+export interface DnsChallenge extends DNSProvider {
+  dns_credential_id: number
+  challenge_method: string
+}
 
 const auto_cert = {
   get_dns_providers(): Promise<DNSProvider[]> {

+ 7 - 0
app/src/api/environment.ts

@@ -5,8 +5,15 @@ export interface Environment extends ModelBase {
   name: string
   url: string
   token: string
+  status?: boolean
 }
 
+export interface Node {
+  id: number
+  name: string
+  token: string
+  response_at?: Date
+}
 const environment: Curd<Environment> = new Curd('/environment')
 
 export default environment

+ 3 - 3
app/src/api/template.ts

@@ -3,10 +3,10 @@ import http from '@/lib/http'
 import type { NgxServer } from '@/api/ngx'
 
 export interface Variable {
-  type: string
-  name: { [key: string]: string }
+  type?: string
+  name?: { [key: string]: string }
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  value: any
+  value?: any
 }
 
 export interface Template extends NgxServer {

+ 9 - 0
app/src/api/upgrade.ts

@@ -1,5 +1,14 @@
 import http from '@/lib/http'
 
+export interface RuntimeInfo {
+  name: string
+  os: string
+  arch: string
+  ex_path: string
+  body: string
+  published_at: string
+}
+
 const upgrade = {
   get_latest_release(channel: string) {
     return http.get('/upgrade/release', {

+ 1 - 1
app/src/components/Chart/RadialBarChart.vue

@@ -7,7 +7,7 @@ import type { Series } from '@/components/Chart/types'
 
 const { series, centerText, colors, name, bottomText }
   = defineProps<{
-    series: Series[]
+    series: Series[] | number[]
     centerText?: string
     colors?: string
     name?: string

+ 3 - 1
app/src/components/Chart/types.d.ts

@@ -1,4 +1,6 @@
+import type {Usage} from '@/api/analytic'
+
 export interface Series {
   name: string
-  data: []
+  data: Usage[]
 }

+ 6 - 4
app/src/components/NodeSelector/NodeSelector.vue

@@ -1,5 +1,7 @@
 <script setup lang="ts">
 import { useGettext } from 'vue3-gettext'
+import type { Ref } from 'vue'
+import type { Environment } from '@/api/environment'
 import environment from '@/api/environment'
 
 const props = defineProps<{
@@ -12,13 +14,13 @@ const emit = defineEmits(['update:target', 'update:map'])
 
 const { $gettext } = useGettext()
 
-const data = ref([])
-const data_map = ref({})
+const data = ref([]) as Ref<Environment[]>
+const data_map = ref({}) as Ref<Record<number, Environment>>
 
 environment.get_list().then(r => {
   data.value = r.data
   r.data.forEach(node => {
-    data_map[node.id] = node
+    data_map.value[node.id] = node
   })
 })
 
@@ -30,7 +32,7 @@ const value = computed({
     if (typeof props.map === 'object') {
       v.forEach(id => {
         if (id !== 0)
-          emit('update:map', { ...props.map, [id]: data_map[id].name })
+          emit('update:map', { ...props.map, [id]: data_map.value[id].name })
       })
     }
     emit('update:target', v)

+ 3 - 6
app/src/components/PageHeader/PageHeader.vue

@@ -1,6 +1,5 @@
 <script setup lang="ts">
 import { useRoute } from 'vue-router'
-import type { Ref } from 'vue'
 import Breadcrumb from '@/components/Breadcrumb/Breadcrumb.vue'
 
 const route = useRoute()
@@ -9,10 +8,8 @@ const display = computed(() => {
   return !route.meta.hiddenHeaderContent
 })
 
-const name = ref(route.name) as Ref<() => string>
-
-watch(() => route.name, () => {
-  name.value = route.name as () => string
+const name = computed(() => {
+  return (route.name as never as () => string)()
 })
 </script>
 
@@ -27,7 +24,7 @@ watch(() => route.name, () => {
         <div class="main">
           <div class="row">
             <h1 class="title">
-              {{ name() }}
+              {{ name }}
             </h1>
             <div class="action">
               <slot name="action" />

+ 1 - 1
app/src/components/SetLanguage/SetLanguage.vue

@@ -28,7 +28,7 @@ watch(current, v => {
   settings.set_language(v)
   gettext.current = v
 
-  const name = route.name as () => string
+  const name = route.name as never as () => string
 
   document.title = `${name()} | Nginx UI`
 })

+ 8 - 6
app/src/components/StdDesign/StdDataDisplay/StdCurd.vue

@@ -15,7 +15,7 @@ export interface StdCurdProps {
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   onClickEdit?: (id: number | string, record: any, index: number) => void
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  beforeSave?: (data: any) => void
+  beforeSave?: (data: any) => Promise<void>
 }
 
 const props = defineProps<StdTableProps & StdCurdProps>()
@@ -24,14 +24,16 @@ const { $gettext } = gettext
 
 const visible = ref(false)
 const update = ref(0)
-const data = reactive({ id: null })
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const data: any = reactive({ id: null })
 
 provide('data', data)
 
-const error = reactive({})
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const error: any = reactive({})
 const selected = ref([])
-
-function onSelect(keys) {
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function onSelect(keys: any) {
   selected.value = keys
 }
 
@@ -86,7 +88,7 @@ function cancel() {
   clear_error()
 }
 
-function edit(id) {
+function edit(id: number | string) {
   props.api!.get(id).then(async r => {
     Object.keys(data).forEach(k => {
       delete data[k]

+ 22 - 18
app/src/components/StdDesign/StdDataDisplay/StdTable.vue

@@ -2,7 +2,7 @@
 import { message } from 'ant-design-vue'
 import { HolderOutlined } from '@ant-design/icons-vue'
 import { useGettext } from 'vue3-gettext'
-import type { ComputedRef } from 'vue'
+import type { ComputedRef, Ref } from 'vue'
 import type { SorterResult } from 'ant-design-vue/lib/table/interface'
 import StdPagination from './StdPagination.vue'
 import StdDataEntry from '@/components/StdDesign/StdDataEntry'
@@ -46,9 +46,11 @@ const props = withDefaults(defineProps<StdTableProps>(), {
 const emit = defineEmits(['onSelected', 'onSelectedRecord', 'clickEdit', 'update:selectedRowKeys', 'clickBatchModify'])
 const { $gettext } = useGettext()
 const route = useRoute()
-const dataSource = ref([])
-const expandKeysList = ref([])
-const rowsKeyIndexMap = ref({})
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const dataSource: Ref<any[]> = ref([])
+const expandKeysList: Ref<number[]> = ref([])
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const rowsKeyIndexMap: Ref<Record<number, any>> = ref({})
 const loading = ref(true)
 
 // This can be useful if there are more than one StdTable in the same page.
@@ -78,7 +80,7 @@ const selectedRowKeysBuffer = computed({
 })
 
 const searchColumns = computed(() => {
-  const _searchColumns = []
+  const _searchColumns: Column[] = []
 
   props.columns?.forEach(column => {
     if (column.search)
@@ -101,7 +103,7 @@ const pithyColumns = computed(() => {
 }) as ComputedRef<Column[]>
 
 const batchColumns = computed(() => {
-  const batch = []
+  const batch: Column[] = []
 
   props.columns?.forEach(column => {
     if (column.batch)
@@ -125,7 +127,7 @@ defineExpose({
   get_list,
 })
 
-function destroy(id) {
+function destroy(id: number | string) {
   props.api!.destroy(id).then(() => {
     get_list()
     message.success($gettext('Deleted successfully'))
@@ -155,9 +157,11 @@ function get_list(page_num = null, page_size = 20) {
     message.error(e?.message ?? $gettext('Server error'))
   })
 }
-function buildIndexMap(data, level: number = 0, index: number = 0, total: number[] = []) {
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function buildIndexMap(data: any, level: number = 0, index: number = 0, total: number[] = []) {
   if (data && data.length > 0) {
-    data.forEach(v => {
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any
+    data.forEach((v: any) => {
       v.level = level
 
       const current_indexes = [...total, index++]
@@ -168,7 +172,7 @@ function buildIndexMap(data, level: number = 0, index: number = 0, total: number
     })
   }
 }
-function orderPaginationChange(_pagination: Pagination, filters, sorter: SorterResult) {
+function orderPaginationChange(_pagination: Pagination, filters: never, sorter: SorterResult) {
   if (sorter) {
     selectedRowKeysBuffer.value = []
     params.order_by = sorter.field
@@ -189,19 +193,19 @@ function orderPaginationChange(_pagination: Pagination, filters, sorter: SorterR
     selectedRowKeysBuffer.value = []
 }
 
-function expandedTable(keys) {
+function expandedTable(keys: number[]) {
   expandKeysList.value = keys
 }
 
-const crossPageSelect = {}
+const crossPageSelect: Record<string, number[]> = {}
 
-async function onSelectChange(_selectedRowKeys) {
+async function onSelectChange(_selectedRowKeys: number[]) {
   const page = params.page || 1
 
-  crossPageSelect[page] = await _selectedRowKeys
+  crossPageSelect[page] = _selectedRowKeys
 
-  let t = []
-  Object.keys(crossPageSelect).forEach(v => {
+  let t: number[] = []
+  Object.keys(crossPageSelect).forEach((v: string) => {
     t.push(...crossPageSelect[v])
   })
 
@@ -215,8 +219,8 @@ async function onSelectChange(_selectedRowKeys) {
   selectedRowKeysBuffer.value = Array.from(set)
   emit('onSelected', selectedRowKeysBuffer.value)
 }
-
-function onSelect(record) {
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+function onSelect(record: any) {
   emit('onSelectedRecord', record)
 }
 

+ 4 - 4
app/src/components/StdDesign/StdDataDisplay/methods/exportCsv.ts

@@ -9,7 +9,7 @@ import type { StdTableProps } from '@/components/StdDesign/StdDataDisplay/StdTab
 
 const { $gettext } = gettext
 async function exportCsv(props: StdTableProps, pithyColumns: ComputedRef<Column[]>) {
-  const header: { title?: string; key: string }[] = []
+  const header: { title?: string; key: string | string[] }[] = []
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   const headerKeys: any[] = []
   const showColumnsMap: Record<string, Column> = {}
@@ -24,8 +24,8 @@ async function exportCsv(props: StdTableProps, pithyColumns: ComputedRef<Column[
       title: t,
       key: column.dataIndex,
     })
-    headerKeys.push(column.dataIndex)
-    showColumnsMap[column.dataIndex] = column
+    headerKeys.push(column.dataIndex.toString())
+    showColumnsMap[column.dataIndex.toString()] = column
   })
 
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -59,7 +59,7 @@ async function exportCsv(props: StdTableProps, pithyColumns: ComputedRef<Column[
       const c = showColumnsMap[key]
 
       _data = c?.customRender?.({ text: _data }) ?? _data
-      obj[c.dataIndex] = _data
+      _.set(obj, c.dataIndex, _data)
     })
     data.push(obj)
   })

+ 2 - 2
app/src/components/StdDesign/StdDataEntry/StdDataEntry.vue

@@ -8,12 +8,12 @@ const props = defineProps<{
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   dataSource: Record<string, any>
   errors?: Record<string, string>
-  layout?: 'horizontal' | 'vertical'
+  layout?: 'horizontal' | 'vertical' | 'inline'
 }>()
 
 const emit = defineEmits<{
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
-  'update:dataSource': (v: any[]) => void
+  'update:dataSource': [data: Record<string, any>]
 }>()
 
 const dataSource = computed({

+ 2 - 2
app/src/components/StdDesign/StdDataEntry/StdFormItem.vue

@@ -7,7 +7,7 @@ const props = defineProps<Props>()
 const { $gettext } = useGettext()
 
 export interface Props {
-  dataIndex?: string
+  dataIndex?: string | string[]
   label?: string
   extra?: string
   error?: {
@@ -16,7 +16,7 @@ export interface Props {
 }
 
 const tag = computed(() => {
-  return props.error?.[props.dataIndex] ?? ''
+  return props.error?.[props.dataIndex!.toString()] ?? ''
 })
 
 const valid_status = computed(() => {

+ 2 - 2
app/src/components/StdDesign/StdDataEntry/components/StdPassword.vue

@@ -4,8 +4,8 @@ import { useGettext } from 'vue3-gettext'
 
 const props = defineProps<{
   value: string
-  generate: boolean
-  placeholder: string
+  generate?: boolean
+  placeholder?: string
 }>()
 
 const emit = defineEmits(['update:value'])

+ 8 - 10
app/src/components/StdDesign/StdDataEntry/components/StdSelector.vue

@@ -6,17 +6,16 @@ import type { Column } from '@/components/StdDesign/types'
 
 const props = defineProps<{
   selectedKey: string | number
-  value: string | number
+  value?: string | number
   recordValueIndex: string
   selectionType: 'radio' | 'checkbox'
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   api: Curd<any>
   columns: Column[]
-  dataKey: string
-  disableSearch: boolean
+  disableSearch?: boolean
   // eslint-disable-next-line @typescript-eslint/no-explicit-any
   getParams: any
-  description: string
+  description?: string
 }>()
 
 const emit = defineEmits(['update:selectedKey', 'changeSelect'])
@@ -29,8 +28,8 @@ onMounted(() => {
 })
 
 const selected = ref([])
-
-const record = reactive({})
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const record: any = reactive({})
 
 function init() {
   if (props.selectedKey && !props.value && props.selectionType === 'radio') {
@@ -113,12 +112,11 @@ const _selectedKey = computed({
         <StdTable
           :api="api"
           :columns="columns"
-          :data_key="dataKey"
-          :disable_search="disableSearch"
+          :disable-search="disableSearch"
           pithy
-          :get_params="getParams"
+          :get-params="getParams"
           :selection-type="selectionType"
-          disable_query_params
+          disable-query-params
           @on-selected="onSelect"
           @on-selected-record="onSelectedRecord"
         />

+ 6 - 3
app/src/components/StdDesign/StdDataEntry/index.tsx

@@ -52,6 +52,7 @@ function textarea(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
 function password(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   return <StdPassword
     v-model:value={dataSource[dataIndex]}
+    value={dataSource[dataIndex]}
     generate={edit.config?.generate}
     placeholder={placeholder_helper(edit)}
   />
@@ -60,19 +61,21 @@ function password(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
 function select(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   return <StdSelect
     v-model:value={dataSource[dataIndex]}
-    mask={edit.mask}
+    value={dataSource[dataIndex]}
+    mask={edit.mask as Record<string, () => string>}
   />
 }
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 function selector(edit: StdDesignEdit, dataSource: any, dataIndex: any) {
   return <StdSelector
     v-model:selectedKey={dataSource[dataIndex]}
+    selectedKey={dataSource[dataIndex]}
     recordValueIndex={edit.selector?.recordValueIndex}
     selectionType={edit.selector?.selectionType}
     api={edit.selector?.api}
     columns={edit.selector?.columns}
-    disableSearch={edit.selector?.disable_search}
-    getParams={edit.selector?.get_params}
+    disableSearch={edit.selector?.disableSearch}
+    getParams={edit.selector?.getParams}
     description={edit.selector?.description}
   />
 }

+ 4 - 6
app/src/components/StdDesign/types.d.ts

@@ -8,20 +8,18 @@ export interface StdDesignEdit {
 
   batch?: boolean // batch edit
 
-  mask?: {
-    [key: string]: () => string
-  } // use for select-option
+  mask?: Record<string, () => string> // use for select-option
 
   rules?: [] // validator rules
 
   selector?: {
-    get_params?: {}
+    getParams?: {}
     recordValueIndex: any // relative to api return
     selectionType: any
     api: Curd,
     valueApi?: Curd,
     columns: any
-    disable_search?: boolean
+    disableSearch?: boolean
     description?: string
     bind?: any
     itemKey?: any // default is id
@@ -52,7 +50,7 @@ export interface Flex {
 
 export interface Column {
   title?: string | (() => string);
-  dataIndex: string;
+  dataIndex: string | string[];
   edit?: StdDesignEdit;
   customRender?: function;
   extra?: string | (() => string);

+ 1 - 1
app/src/layouts/HeaderLayout.vue

@@ -9,7 +9,7 @@ import NginxControl from '@/components/NginxControl/NginxControl.vue'
 import SwitchAppearance from '@/components/SwitchAppearance/SwitchAppearance.vue'
 
 const emit = defineEmits<{
-  clickUnFold: () => void
+  clickUnFold: [void]
 }>()
 
 const { $gettext } = gettext

+ 5 - 5
app/src/layouts/SideBar.vue

@@ -3,6 +3,7 @@ import { useRoute } from 'vue-router'
 import type { ComputedRef } from 'vue'
 import { computed, ref, watch } from 'vue'
 import type { AntdIconType } from '@ant-design/icons-vue/lib/components/AntdIcon'
+import type { IconComponentProps } from '@ant-design/icons-vue/es/components/Icon'
 import Logo from '@/components/Logo/Logo.vue'
 import { routes } from '@/routes'
 import EnvIndicator from '@/components/EnvIndicator/EnvIndicator.vue'
@@ -56,7 +57,7 @@ const visible: ComputedRef<sidebar[]> = computed(() => {
     const t: sidebar = {
       path: s.path,
       name: s.name,
-      meta: s.meta as meta,
+      meta: s.meta as unknown as meta,
       children: [],
     };
 
@@ -64,7 +65,7 @@ const visible: ComputedRef<sidebar[]> = computed(() => {
       if (c.meta && c.meta.hiddenInSidebar)
         return
 
-      t.children.push((c as sidebar))
+      t.children.push((c as unknown as sidebar))
     })
     res.push(t)
   })
@@ -80,7 +81,6 @@ const visible: ComputedRef<sidebar[]> = computed(() => {
     <AMenu
       v-model:openKeys="openKeys"
       v-model:selectedKeys="selectedKey"
-      :open-keys="openKeys"
       mode="inline"
     >
       <EnvIndicator />
@@ -91,7 +91,7 @@ const visible: ComputedRef<sidebar[]> = computed(() => {
           :key="s.name"
           @click="$router.push(`/${s.path}`).catch(() => {})"
         >
-          <component :is="s.meta.icon" />
+          <Component :is="s.meta.icon as IconComponentProps" />
           <span>{{ s.name() }}</span>
         </AMenuItem>
 
@@ -100,7 +100,7 @@ const visible: ComputedRef<sidebar[]> = computed(() => {
           :key="s.path"
         >
           <template #title>
-            <component :is="s.meta.icon" />
+            <Component :is="s.meta.icon as IconComponentProps" />
             <span>{{ s.name() }}</span>
           </template>
           <AMenuItem

+ 1 - 1
app/src/routes/index.ts

@@ -1,4 +1,5 @@
 import { createRouter, createWebHashHistory } from 'vue-router'
+import type { AntDesignOutlinedIconType } from '@ant-design/icons-vue/lib/icons/AntDesignOutlined'
 
 import {
   CloudOutlined,
@@ -13,7 +14,6 @@ import {
   UserOutlined,
 } from '@ant-design/icons-vue'
 import NProgress from 'nprogress'
-import type { AntDesignOutlinedIconType } from '@ant-design/icons-vue/lib/icons/AntDesignOutlined'
 
 import gettext from '../gettext'
 import { useUserStore } from '@/pinia'

+ 1 - 1
app/src/version.json

@@ -1 +1 @@
-{"version":"2.0.0-beta.4","build_id":56,"total_build":260}
+{"version":"2.0.0-beta.4","build_id":62,"total_build":266}

+ 10 - 10
app/src/views/cert/Cert.vue

@@ -9,13 +9,14 @@ import cert from '@/api/cert'
 import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 import CertInfo from '@/views/domain/cert/CertInfo.vue'
+import type { Column } from '@/components/StdDesign/types'
 
-const { $gettext, interpolate } = useGettext()
+const { $gettext } = useGettext()
 
-const columns = [{
+const columns: Column[] = [{
   title: () => $gettext('Name'),
   dataIndex: 'name',
-  sorter: true,
+  sortable: true,
   pithy: true,
   customRender: (args: customRender) => {
     const { text, record } = args
@@ -31,7 +32,7 @@ const columns = [{
 }, {
   title: () => $gettext('Config Name'),
   dataIndex: 'filename',
-  sorter: true,
+  sortable: true,
   pithy: true,
 }, {
   title: () => $gettext('Auto Cert'),
@@ -50,7 +51,7 @@ const columns = [{
 
     return h('div', template)
   },
-  sorter: true,
+  sortable: true,
   pithy: true,
 }, {
   title: () => $gettext('SSL Certificate Path'),
@@ -58,19 +59,19 @@ const columns = [{
   edit: {
     type: input,
   },
-  display: false,
+  hidden: true,
 }, {
   title: () => $gettext('SSL Certificate Key Path'),
   dataIndex: 'ssl_certificate_key_path',
   edit: {
     type: input,
   },
-  display: false,
+  hidden: true,
 }, {
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
   customRender: datetime,
-  sorter: true,
+  sortable: true,
   pithy: true,
 }, {
   title: () => $gettext('Action'),
@@ -83,7 +84,6 @@ const columns = [{
     :title="$gettext('Certification')"
     :api="cert"
     :columns="columns"
-    row-key="name"
   >
     <template #beforeEdit="{ data }">
       <template v-if="data.auto_cert === 1">
@@ -109,7 +109,7 @@ const columns = [{
           style="margin-bottom: 15px"
         >
           <AAlert
-            :message="interpolate($gettext('Domains list is empty, try to reopen auto-cert for %{config}'), { config: data.filename })"
+            :message="$gettext('Domains list is empty, try to reopen auto-cert for %{config}', { config: data.filename })"
             type="error"
             show-icon
           />

+ 13 - 6
app/src/views/cert/DNSChallenge.vue

@@ -1,12 +1,16 @@
 <script setup lang="ts">
 import { useGettext } from 'vue3-gettext'
 import type { SelectProps } from 'ant-design-vue'
+import type { Ref } from 'vue'
+import type { DNSProvider } from '@/api/auto_cert'
 import auto_cert from '@/api/auto_cert'
 
 const { $gettext } = useGettext()
-const providers = ref([])
+const providers = ref([]) as Ref<DNSProvider[]>
 
-const data = inject('data')
+// This data is provided by the Top StdCurd component,
+// is the object that you are trying to modify it
+const data = inject('data') as DNSProvider
 
 const code = computed(() => {
   return data.code
@@ -14,9 +18,11 @@ const code = computed(() => {
 
 const provider_idx = ref()
 function init() {
-  data.configuration = {
-    credentials: {},
-    additional: {},
+  if (!data.configuration) {
+    data.configuration = {
+      credentials: {},
+      additional: {},
+    }
   }
   providers.value?.forEach((v: { code: string }, k: number) => {
     if (v.code === code.value)
@@ -39,6 +45,7 @@ watch(code, init)
 watch(current, () => {
   data.code = current.value.code
   data.provider = current.value.name
+
   auto_cert.get_dns_provider(current.value.code).then(r => {
     Object.assign(current.value, r)
   })
@@ -47,7 +54,7 @@ watch(current, () => {
 const options = computed<SelectProps['options']>(() => {
   const list: SelectProps['options'] = []
 
-  providers.value.forEach((v: { name: string }, k: number) => {
+  providers.value.forEach((v: DNSProvider, k: number) => {
     list!.push({
       value: k,
       label: v.name,

+ 5 - 5
app/src/views/cert/DNSCredential.vue

@@ -5,13 +5,14 @@ import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransfor
 import dns_credential from '@/api/dns_credential'
 import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import { input } from '@/components/StdDesign/StdDataEntry'
+import type { Column } from '@/components/StdDesign/types'
 
 const { $gettext } = useGettext()
 
-const columns = [{
+const columns: Column[] = [{
   title: () => $gettext('Name'),
   dataIndex: 'name',
-  sorter: true,
+  sortable: true,
   pithy: true,
   edit: {
     type: input,
@@ -19,13 +20,13 @@ const columns = [{
 }, {
   title: () => $gettext('Provider'),
   dataIndex: ['config', 'name'],
-  sorter: true,
+  sortable: true,
   pithy: true,
 }, {
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
   customRender: datetime,
-  sorter: true,
+  sortable: true,
   pithy: true,
 }, {
   title: () => $gettext('Action'),
@@ -38,7 +39,6 @@ const columns = [{
     :title="$gettext('DNS Credentials')"
     :api="dns_credential"
     :columns="columns"
-    row-key="name"
   >
     <template #beforeEdit>
       <AAlert

+ 4 - 3
app/src/views/config/ConfigEdit.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { useRoute } from 'vue-router'
-import { computed, ref } from 'vue'
 import { message } from 'ant-design-vue'
+import type { Ref } from 'vue'
 import { formatDateTime } from '@/lib/helper'
 import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
 import gettext from '@/gettext'
@@ -10,6 +10,7 @@ import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 import ngx from '@/api/ngx'
 import InspectConfig from '@/views/config/InspectConfig.vue'
 import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
+import type { ChatComplicationMessage } from '@/api/openai'
 
 const { $gettext, interpolate } = gettext
 const route = useRoute()
@@ -25,7 +26,7 @@ const name = computed(() => {
 })
 
 const configText = ref('')
-const history_chatgpt_record = ref([])
+const history_chatgpt_record = ref([]) as Ref<ChatComplicationMessage[]>
 const file_path = ref('')
 const active_key = ref(['1', '2'])
 const modified_at = ref('')
@@ -52,7 +53,7 @@ init()
 
 function save() {
   config.save(name.value, { content: configText.value }).then(r => {
-    configText.value = r.config
+    configText.value = r.content
     message.success($gettext('Saved successfully'))
   }).catch(r => {
     message.error(interpolate($gettext('Save error %{msg}'), { msg: r.message ?? '' }))

+ 13 - 16
app/src/views/dashboard/Environments.vue

@@ -1,23 +1,23 @@
 <script setup lang="ts">
 import { useGettext } from 'vue3-gettext'
-import { computed, onMounted, onUnmounted, ref } from 'vue'
 import Icon, { LinkOutlined, SendOutlined, ThunderboltOutlined } from '@ant-design/icons-vue'
 import type ReconnectingWebSocket from 'reconnecting-websocket'
+import type { Ref } from 'vue'
 import { useSettingsStore } from '@/pinia'
+import type { Node } from '@/api/environment'
 import environment from '@/api/environment'
 import logo from '@/assets/img/logo.png'
 import pulse from '@/assets/svg/pulse.svg'
 import { formatDateTime } from '@/lib/helper'
-import ws from '@/lib/websocket'
 import NodeAnalyticItem from '@/views/dashboard/components/NodeAnalyticItem.vue'
+import analytic from '@/api/analytic'
 
-const settingsStore = useSettingsStore()
 const { $gettext } = useGettext()
 
-const data = ref([])
+const data = ref([]) as Ref<Node[]>
 
 const node_map = computed(() => {
-  const o = {}
+  const o = {} as Record<number, Node>
 
   data.value.forEach(v => {
     o[v.id] = v
@@ -32,16 +32,19 @@ onMounted(() => {
   environment.get_list().then(r => {
     data.value = r.data
   })
-  websocket = ws('/api/analytic/nodes')
-  websocket.onmessage = m => {
+  websocket = analytic.nodes()
+  websocket.onmessage = async m => {
     const nodes = JSON.parse(m.data)
-    for (const key in nodes) {
+
+    Object.keys(nodes).forEach((v: string) => {
+      const key = Number.parseInt(v)
+
       // update node online status
       if (node_map.value[key]) {
         Object.assign(node_map.value[key], nodes[key])
         node_map.value[key].response_at = new Date()
       }
-    }
+    })
   }
 })
 
@@ -49,13 +52,7 @@ onUnmounted(() => {
   websocket.close()
 })
 
-export interface Node {
-  id: number
-  name: string
-  token: string
-}
-
-const { environment: env } = settingsStore
+const { environment: env } = useSettingsStore()
 
 function link_start(node: Node) {
   env.id = node.id

+ 38 - 33
app/src/views/dashboard/ServerAnalytic.vue

@@ -3,9 +3,10 @@ import { useGettext } from 'vue3-gettext'
 import type ReconnectingWebSocket from 'reconnecting-websocket'
 import AreaChart from '@/components/Chart/AreaChart.vue'
 import RadialBarChart from '@/components/Chart/RadialBarChart.vue'
+import type { CPUInfoStat, DiskStat, HostInfoStat, LoadStat, MemStat } from '@/api/analytic'
 import analytic from '@/api/analytic'
-import ws from '@/lib/websocket'
 import { bytesToSize } from '@/lib/helper'
+import type { Series } from '@/components/Chart/types'
 
 const { $gettext } = useGettext()
 
@@ -17,32 +18,42 @@ const host = reactive({
   os: '',
   kernelVersion: '',
   kernelArch: '',
-})
+}) as HostInfoStat
 
 const cpu = ref('0.0')
-const cpu_info = reactive([])
-const cpu_analytic_series = reactive([{ name: 'User', data: [] }, { name: 'Total', data: [] }])
+const cpu_info = reactive([]) as CPUInfoStat[]
+const cpu_analytic_series = reactive([{ name: 'User', data: [] }, { name: 'Total', data: [] }]) as Series[]
 
 const net_analytic = reactive([{ name: $gettext('Receive'), data: [] },
-  { name: $gettext('Send'), data: [] }])
+  { name: $gettext('Send'), data: [] }]) as Series[]
 
 const disk_io_analytic = reactive([{ name: $gettext('Writes'), data: [] },
-  { name: $gettext('Reads'), data: [] }])
-
-const memory = reactive({ swap_used: '', swap_percent: '', swap_total: '' })
-const disk = reactive({ percentage: '', used: '' })
+  { name: $gettext('Reads'), data: [] }]) as Series[]
+
+const memory = reactive({
+  total: '',
+  used: '',
+  cached: '',
+  free: '',
+  swap_used: '',
+  swap_cached: '',
+  swap_percent: 0,
+  swap_total: '',
+  pressure: 0,
+}) as MemStat
+
+const disk = reactive({ percentage: 0, used: '', total: '', writes: { x: '', y: 0 }, reads: { x: '', y: 0 } }) as DiskStat
 const disk_io = reactive({ writes: 0, reads: 0 })
 const uptime = ref('')
-const loadavg = reactive({ load1: 0, load5: 0, load15: 0 })
+const loadavg = reactive({ load1: 0, load5: 0, load15: 0 }) as LoadStat
 const net = reactive({ recv: 0, sent: 0, last_recv: 0, last_sent: 0 })
 
 const net_formatter = (bytes: number) => {
   return `${bytesToSize(bytes)}/s`
 }
 
-interface Usage {
-  x: number
-  y: number
+const cpu_formatter = (usage: number) => {
+  return usage.toFixed(2)
 }
 
 onMounted(() => {
@@ -60,22 +71,15 @@ onMounted(() => {
 
     net.last_recv = r.network.init.bytesRecv
     net.last_sent = r.network.init.bytesSent
-    r.cpu.user.forEach((u: Usage) => {
-      cpu_analytic_series[0].data.push([u.x, u.y.toFixed(2)])
-    })
-    r.cpu.total.forEach((u: Usage) => {
-      cpu_analytic_series[1].data.push([u.x, u.y.toFixed(2)])
-    })
-    r.network.bytesRecv.forEach((u: Usage) => {
-      net_analytic[0].data.push([u.x, u.y.toFixed(2)])
-    })
-    r.network.bytesSent.forEach((u: Usage) => {
-      net_analytic[1].data.push([u.x, u.y.toFixed(2)])
-    })
+
+    cpu_analytic_series[0].data = cpu_analytic_series[0].data.concat(r.cpu.user)
+    cpu_analytic_series[1].data = cpu_analytic_series[1].data.concat(r.cpu.total)
+    net_analytic[0].data = net_analytic[0].data.concat(r.network.bytesRecv)
+    net_analytic[1].data = net_analytic[1].data.concat(r.network.bytesSent)
     disk_io_analytic[0].data = disk_io_analytic[0].data.concat(r.disk_io.writes)
     disk_io_analytic[1].data = disk_io_analytic[1].data.concat(r.disk_io.reads)
 
-    websocket = ws('/api/analytic')
+    websocket = analytic.server()
     websocket.onmessage = wsOnMessage
   })
 })
@@ -97,17 +101,17 @@ function handle_uptime(t: number) {
   uptime.value = `${uptime_days}d ${uptime_hours}h ${Math.floor(_uptime / 60)}m`
 }
 
-function wsOnMessage(m) {
+function wsOnMessage(m: MessageEvent) {
   const r = JSON.parse(m.data)
 
-  const cpu_usage = r.cpu.system + r.cpu.user
+  const cpu_usage = Math.min(r.cpu.system + r.cpu.user, 100)
 
   cpu.value = cpu_usage.toFixed(2)
 
-  const time = new Date().getTime()
+  const time = new Date().toLocaleString()
 
-  cpu_analytic_series[0].data.push([time, r.cpu.user.toFixed(2)])
-  cpu_analytic_series[1].data.push([time, cpu.value])
+  cpu_analytic_series[0].data.push({ x: time, y: r.cpu.user.toFixed(2) })
+  cpu_analytic_series[1].data.push({ x: time, y: cpu_usage })
 
   if (cpu_analytic_series[0].data.length > 100) {
     cpu_analytic_series[0].data.shift()
@@ -135,8 +139,8 @@ function wsOnMessage(m) {
   net.last_recv = r.network.bytesRecv
   net.last_sent = r.network.bytesSent
 
-  net_analytic[0].data.push([time, net.recv])
-  net_analytic[1].data.push([time, net.sent])
+  net_analytic[0].data.push({ x: time, y: net.recv })
+  net_analytic[1].data.push({ x: time, y: net.sent })
 
   if (net_analytic[0].data.length > 100) {
     net_analytic[0].data.shift()
@@ -302,6 +306,7 @@ function wsOnMessage(m) {
           </AStatistic>
           <AreaChart
             :series="cpu_analytic_series"
+            :y-formatter="cpu_formatter"
             :max="100"
           />
         </ACard>

+ 4 - 4
app/src/views/domain/DomainAdd.vue

@@ -67,10 +67,10 @@ function create_another() {
 
 const has_server_name = computed(() => {
   const servers = ngx_config.servers
-  for (const server_key in servers) {
-    for (const k in servers[server_key].directives) {
-      const v = servers[server_key].directives[k]
-      if (v.directive === 'server_name' && v.params.trim() !== '')
+
+  for (const server of Object.values(servers)) {
+    for (const directive of Object.values(server.directives!)) {
+      if (directive.directive === 'server_name' && directive.params.trim() !== '')
         return true
     }
   }

+ 11 - 11
app/src/views/domain/DomainEdit.vue

@@ -2,15 +2,19 @@
 import { useGettext } from 'vue3-gettext'
 import { useRoute, useRouter } from 'vue-router'
 import { message } from 'ant-design-vue'
+import type { Ref } from 'vue'
 import FooterToolBar from '@/components/FooterToolbar/FooterToolBar.vue'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 
 import NgxConfigEditor from '@/views/domain/ngx_conf/NgxConfigEditor.vue'
+import type { Site } from '@/api/domain'
 import domain from '@/api/domain'
 import type { NgxConfig } from '@/api/ngx'
 import ngx from '@/api/ngx'
 import config from '@/api/config'
 import RightSettings from '@/views/domain/components/RightSettings.vue'
+import type { CertificateInfo } from '@/api/cert'
+import type { ChatComplicationMessage } from '@/api/openai'
 
 const { $gettext, interpolate } = useGettext()
 
@@ -29,7 +33,7 @@ const ngx_config: NgxConfig = reactive({
   servers: [],
 })
 
-const cert_info_map = reactive({})
+const cert_info_map: Record<string, CertificateInfo> = reactive({})
 
 const auto_cert = ref(false)
 const enabled = ref(false)
@@ -52,16 +56,16 @@ const advance_mode = computed({
   },
 })
 
-const history_chatgpt_record = ref([])
+const history_chatgpt_record = ref([]) as Ref<ChatComplicationMessage[]>
 
-function handle_response(r) {
+function handle_response(r: Site) {
   if (r.advanced)
     advance_mode.value = true
 
   if (r.advanced)
     advance_mode.value = true
 
-  Object.keys(cert_info_map).forEach(v => {
+  Object.keys(cert_info_map).forEach((v: string) => {
     delete cert_info_map[v]
   })
   parse_error_status.value = false
@@ -87,13 +91,13 @@ function init() {
   }
 }
 
-function handle_parse_error(e) {
+function handle_parse_error(e: { error?: string; message: string }) {
   console.error(e)
   if (e?.error === 'nginx_config_syntax_error') {
     parse_error_status.value = true
     parse_error_message.value = e.message
     config.get(`sites-available/${name.value}`).then(r => {
-      configText.value = r.config
+      configText.value = r.content
     })
   }
   else {
@@ -117,7 +121,7 @@ function on_mode_change(advanced: boolean) {
   })
 }
 
-function build_config() {
+async function build_config() {
   return ngx.build_config(ngx_config).then(r => {
     configText.value = r.content
   })
@@ -316,10 +320,6 @@ provide('data', data)
   opacity: 0;
 }
 
-.location-block {
-
-}
-
 .directive-params-wrapper {
   margin: 10px 0;
 }

+ 8 - 7
app/src/views/domain/DomainList.vue

@@ -8,13 +8,14 @@ import domain from '@/api/domain'
 import { input } from '@/components/StdDesign/StdDataEntry'
 import SiteDuplicate from '@/views/domain/components/SiteDuplicate.vue'
 import InspectConfig from '@/views/config/InspectConfig.vue'
+import type { Column } from '@/components/StdDesign/types'
 
 const { $gettext } = useGettext()
 
-const columns = [{
+const columns: Column[] = [{
   title: () => $gettext('Name'),
   dataIndex: 'name',
-  sorter: true,
+  sortable: true,
   pithy: true,
   edit: {
     type: input,
@@ -37,13 +38,13 @@ const columns = [{
 
     return h('div', template)
   },
-  sorter: true,
+  sortable: true,
   pithy: true,
 }, {
   title: () => $gettext('Updated at'),
   dataIndex: 'modified_at',
   customRender: datetime,
-  sorter: true,
+  sortable: true,
   pithy: true,
 }, {
   title: () => $gettext('Action'),
@@ -52,7 +53,7 @@ const columns = [{
 
 const table = ref()
 
-function enable(name) {
+function enable(name: string) {
   domain.enable(name).then(() => {
     message.success($gettext('Enabled successfully'))
     table.value?.get_list()
@@ -61,7 +62,7 @@ function enable(name) {
   })
 }
 
-function disable(name) {
+function disable(name: string) {
   domain.disable(name).then(() => {
     message.success($gettext('Disabled successfully'))
     table.value?.get_list()
@@ -70,7 +71,7 @@ function disable(name) {
   })
 }
 
-function destroy(site_name) {
+function destroy(site_name: string) {
   domain.destroy(site_name).then(() => {
     table.value.get_list()
     message.success($gettext('Delete site: %{site_name}', { site_name }))

+ 1 - 1
app/src/views/domain/cert/CertInfo.vue

@@ -37,7 +37,7 @@ const now = computed(() => new Date().toISOString())
       </template>
       <template v-else>
         <CheckCircleOutlined class="text-green-500" />
-        <span class="ml-2">{{ $gettext('Certificate is valid<') }}</span>
+        <span class="ml-2">{{ $gettext('Certificate is valid') }}</span>
       </template>
     </div>
   </div>

+ 14 - 11
app/src/views/domain/cert/ChangeCert.vue

@@ -1,23 +1,26 @@
 <script setup lang="tsx">
 import { useGettext } from 'vue3-gettext'
 import { Badge } from 'ant-design-vue'
+import type { ComputedRef, Ref } from 'vue'
 import StdTable from '@/components/StdDesign/StdDataDisplay/StdTable.vue'
+import type { Cert } from '@/api/cert'
 import cert from '@/api/cert'
 import type { customRender } from '@/components/StdDesign/StdDataDisplay/StdTableTransformer'
 import { input } from '@/components/StdDesign/StdDataEntry'
 import type { NgxDirective } from '@/api/ngx'
+import type { Column } from '@/components/StdDesign/types'
 
 const { $gettext } = useGettext()
 
-const current_server_directives = inject('current_server_directives')
-const directivesMap = inject('directivesMap') as Record<string, NgxDirective[]>
+const current_server_directives = inject('current_server_directives') as ComputedRef<NgxDirective[]>
+const directivesMap = inject('directivesMap') as Ref<Record<string, NgxDirective[]>>
 const visible = ref(false)
-const record = ref({})
+const record = ref({}) as Ref<Cert>
 
-const columns = [{
+const columns: Column[] = [{
   title: () => $gettext('Name'),
   dataIndex: 'name',
-  sorter: true,
+  sortable: true,
   pithy: true,
   customRender: (args: customRender) => {
     const { text, record: r } = args
@@ -47,7 +50,7 @@ const columns = [{
 
     return h('div', template)
   },
-  sorter: true,
+  sortable: true,
   pithy: true,
 }]
 
@@ -55,13 +58,13 @@ function open() {
   visible.value = true
 }
 
-function onSelectedRecord(r) {
+function onSelectedRecord(r: Cert) {
   record.value = r
 }
 
 function ok() {
-  if (directivesMap.ssl_certificate?.[0]) {
-    directivesMap.ssl_certificate[0].params = record.value.ssl_certificate_path
+  if (directivesMap.value.ssl_certificate?.[0]) {
+    directivesMap.value.ssl_certificate[0].params = record.value.ssl_certificate_path
   }
   else {
     current_server_directives?.value.push({
@@ -69,8 +72,8 @@ function ok() {
       params: record.value.ssl_certificate_path,
     })
   }
-  if (directivesMap.ssl_certificate_key?.[0]) {
-    directivesMap.ssl_certificate_key[0].params = record.value.ssl_certificate_key_path
+  if (directivesMap.value.ssl_certificate_key?.[0]) {
+    directivesMap.value.ssl_certificate_key[0].params = record.value.ssl_certificate_key_path
   }
   else {
     current_server_directives?.value.push({

+ 8 - 5
app/src/views/domain/cert/IssueCert.vue

@@ -3,16 +3,19 @@ import { useGettext } from 'vue3-gettext'
 import ObtainCert from '@/views/domain/cert/components/ObtainCert.vue'
 import type { NgxDirective } from '@/api/ngx'
 
-const props = defineProps<{
+export interface Props {
   enabled: boolean
-}>()
+  configName: string
+}
+
+const props = defineProps<Props>()
 
 const emit = defineEmits(['callback', 'update:enabled'])
 
 const { $gettext } = useGettext()
 const issuing_cert = ref(false)
 const obtain_cert = ref()
-const directivesMap = inject('directivesMap') as Record<string, NgxDirective[]>
+const directivesMap = inject('directivesMap') as Ref<Record<string, NgxDirective[]>>
 
 const enabled = computed({
   get() {
@@ -24,10 +27,10 @@ const enabled = computed({
 })
 
 const no_server_name = computed(() => {
-  if (!directivesMap.server_name)
+  if (!directivesMap.value.server_name)
     return true
 
-  return directivesMap.server_name.length === 0
+  return directivesMap.value.server_name.length === 0
 })
 
 provide('no_server_name', no_server_name)

+ 18 - 21
app/src/views/domain/cert/components/AutoCertStepOne.vue

@@ -1,11 +1,13 @@
 <script setup lang="ts">
-import { inject } from 'vue'
 import { useGettext } from 'vue3-gettext'
+import type { DnsChallenge } from '@/api/auto_cert'
 import DNSChallenge from '@/views/domain/cert/components/DNSChallenge.vue'
 
 const { $gettext } = useGettext()
 const no_server_name = inject('no_server_name')
-const data = inject('data')
+
+// Provide by ObtainCert.vue
+const data = inject('data') as DnsChallenge
 </script>
 
 <template>
@@ -30,28 +32,23 @@ const data = inject('data')
     :message="$gettext('Note')"
   >
     <template #description>
-      <p v-translate>
-        The server_name
-        in the current configuration must be the domain name you need to get the certificate, support
-        multiple domains.
+      <p>
+        {{ $gettext('The server_name'
+          + 'in the current configuration must be the domain name you need to get the certificate, support'
+          + 'multiple domains.') }}
       </p>
-      <p v-translate>
-        The certificate for the domain will be checked every hour,
-        and will be renewed if it has been more than 1 week since it was last issued.
+      <p>
+        {{ $gettext('The certificate for the domain will be checked 5 minutes,'
+          + 'and will be renewed if it has been more than 1 week since it was last issued.') }}
       </p>
-      <p
-        v-if="data.challenge_method === 'http01'"
-        v-translate
-      >
-        Make sure you have configured a reverse proxy for .well-known
-        directory to HTTPChallengePort before obtaining the certificate.
+      <p v-if="data.challenge_method === 'http01'">
+        {{ $gettext('Make sure you have configured a reverse proxy for .well-known '
+          + 'directory to HTTPChallengePort before obtaining the certificate.') }}
       </p>
-      <p
-        v-else-if="data.challenge_method === 'dns01'"
-        v-translate
-      >
-        Please first add credentials in Certification > DNS Credentials, and then select one of the credentials
-        below to request the API of the DNS provider.
+      <p v-else-if="data.challenge_method === 'dns01'">
+        {{ $gettext('Please first add credentials in Certification > DNS Credentials, '
+          + 'and then select one of the credentials'
+          + 'below to request the API of the DNS provider.') }}
       </p>
     </template>
   </AAlert>

+ 11 - 5
app/src/views/domain/cert/components/DNSChallenge.vue

@@ -1,14 +1,20 @@
 <script setup lang="ts">
 import { useGettext } from 'vue3-gettext'
 import type { SelectProps } from 'ant-design-vue'
+import type { Ref } from 'vue'
+import type { DNSProvider } from '@/api/auto_cert'
 import auto_cert from '@/api/auto_cert'
 import dns_credential from '@/api/dns_credential'
 
 const { $gettext } = useGettext()
-const providers = ref([])
-const credentials = ref([])
+const providers = ref([]) as Ref<DNSProvider[]>
+const credentials = ref<SelectProps['options']>([])
 
-const data = inject('data')
+// This data is provided by the Top StdCurd component,
+// is the object that you are trying to modify it
+// we externalize the dns_credential_id to the parent component,
+// this is used to tell the backend which dns_credential to use
+const data = inject('data') as DNSProvider & { dns_credential_id: number | null }
 
 const code = computed(() => {
   return data.code
@@ -16,7 +22,7 @@ const code = computed(() => {
 
 const provider_idx = ref()
 function init() {
-  providers.value?.forEach((v, k: number) => {
+  providers.value?.forEach((v: DNSProvider, k: number) => {
     if (v.code === code.value)
       provider_idx.value = k
   })
@@ -42,7 +48,7 @@ watch(current, () => {
 
   dns_credential.get_list({ provider: data.provider }).then(r => {
     r.data.forEach(v => {
-      credentials.value.push({
+      credentials.value?.push({
         value: v.id,
         label: v.name,
       })

+ 30 - 28
app/src/views/domain/cert/components/ObtainCert.vue

@@ -2,12 +2,14 @@
 import { useGettext } from 'vue3-gettext'
 import Modal from 'ant-design-vue/lib/modal'
 import { message } from 'ant-design-vue'
+import type { Ref } from 'vue'
 import websocket from '@/lib/websocket'
 import template from '@/api/template'
 import domain from '@/api/domain'
 import AutoCertStepOne from '@/views/domain/cert/components/AutoCertStepOne.vue'
-import type DNSChallenge from '@/views/domain/cert/components/DNSChallenge.vue'
-import type { NgxDirective } from '@/api/ngx'
+import type { NgxConfig, NgxDirective } from '@/api/ngx'
+import type { Props } from '@/views/domain/cert/IssueCert.vue'
+import type { DnsChallenge } from '@/api/auto_cert'
 
 const emit = defineEmits(['update:auto_cert'])
 
@@ -15,14 +17,14 @@ const { $gettext, interpolate } = useGettext()
 
 const modalVisible = ref(false)
 const step = ref(1)
-const directivesMap = inject('directivesMap') as Record<string, NgxDirective[]>
+const directivesMap = inject('directivesMap') as Ref<Record<string, NgxDirective[]>>
 
 const progressStrokeColor = {
   from: '#108ee9',
   to: '#87d068',
 }
 
-const data: DNSChallenge = reactive({
+const data: DnsChallenge = reactive({
   dns_credential_id: 0,
   challenge_method: 'http01',
   code: '',
@@ -40,15 +42,15 @@ provide('data', data)
 
 const logContainer = ref()
 
-const save_site_config = inject('save_site_config')
-const no_server_name = inject('no_server_name')
-const props = inject('props')
-const issuing_cert = inject('issuing_cert')
-const ngx_config = inject('ngx_config')
-const current_server_directives = inject('current_server_directives')
+const save_site_config = inject('save_site_config') as () => Promise<void>
+const no_server_name = inject('no_server_name') as Ref<boolean>
+const props = inject('props') as Props
+const issuing_cert = inject('issuing_cert') as Ref<boolean>
+const ngx_config = inject('ngx_config') as NgxConfig
+const current_server_directives = inject('current_server_directives') as NgxDirective[]
 
 const name = computed(() => {
-  return directivesMap.server_name[0].params.trim()
+  return directivesMap.value.server_name[0].params.trim()
 })
 
 const issue_cert = async (config_name: string, server_name: string) => {
@@ -101,9 +103,9 @@ const issue_cert = async (config_name: string, server_name: string) => {
 }
 
 async function callback(ssl_certificate: string, ssl_certificate_key: string) {
-  directivesMap.ssl_certificate[0].params = ssl_certificate
-  directivesMap.ssl_certificate_key[0].params = ssl_certificate_key
-  save_site_config()
+  directivesMap.value.ssl_certificate[0].params = ssl_certificate
+  directivesMap.value.ssl_certificate_key[0].params = ssl_certificate_key
+  await save_site_config()
 }
 
 function change_auto_cert(status: boolean) {
@@ -119,7 +121,7 @@ function change_auto_cert(status: boolean) {
     })
   }
   else {
-    domain.remove_auto_cert(props.config_name).then(() => {
+    domain.remove_auto_cert(props.configName).then(() => {
       message.success(interpolate($gettext('Auto-renewal disabled for %{name}'), { name: name.value }))
     }).catch(e => {
       message.error(e.message ?? interpolate($gettext('Disable auto-renewal failed for %{name}'), { name: name.value }))
@@ -131,23 +133,23 @@ async function onchange(status: boolean) {
   if (status) {
     await template.get_block('letsencrypt.conf').then(r => {
       ngx_config.servers.forEach(async v => {
-        v.locations = v.locations.filter(l => l.path !== '/.well-known/acme-challenge')
+        v.locations = v?.locations?.filter(l => l.path !== '/.well-known/acme-challenge')
 
-        v.locations.push(...r.locations)
+        v.locations?.push(...r.locations)
       })
     }).then(async () => {
       // if ssl_certificate is empty, do not save, just use the config from last step.
-      if (directivesMap.ssl_certificate?.[0])
+      if (directivesMap.value.ssl_certificate?.[0])
         await save_site_config()
 
       job()
     })
   }
   else {
-    await ngx_config.servers.forEach(v => {
-      v.locations = v.locations.filter(l => l.path !== '/.well-known/acme-challenge')
+    ngx_config.servers.forEach(v => {
+      v.locations = v?.locations?.filter(l => l.path !== '/.well-known/acme-challenge')
     })
-    save_site_config()
+    await save_site_config()
     change_auto_cert(status)
   }
 
@@ -165,26 +167,26 @@ function job() {
     return
   }
 
-  const server_name = directivesMap.server_name[0]
+  const server_name_idx = directivesMap.value.server_name[0]?.idx ?? 0
 
-  if (!directivesMap.ssl_certificate) {
-    current_server_directives.splice(server_name.idx + 1, 0, {
+  if (!directivesMap.value.ssl_certificate) {
+    current_server_directives.splice(server_name_idx + 1, 0, {
       directive: 'ssl_certificate',
       params: '',
     })
   }
 
   nextTick(() => {
-    if (!directivesMap.ssl_certificate_key) {
-      const ssl_certificate = directivesMap.ssl_certificate[0]
+    if (!directivesMap.value.ssl_certificate_key) {
+      const ssl_certificate_idx = directivesMap.value.ssl_certificate[0]?.idx ?? 0
 
-      current_server_directives.splice(ssl_certificate.idx + 1, 0, {
+      current_server_directives.splice(ssl_certificate_idx + 1, 0, {
         directive: 'ssl_certificate_key',
         params: '',
       })
     }
   }).then(() => {
-    issue_cert(props.config_name, name.value)
+    issue_cert(props.configName, name.value)
   })
 }
 

+ 1 - 1
app/src/views/domain/components/Deploy.vue

@@ -13,7 +13,7 @@ const node_map = reactive({})
 const target = ref([])
 const overwrite = ref(false)
 const enabled = ref(false)
-const name = inject('name')
+const name = inject('name') as Ref<string>
 
 function deploy() {
   Modal.confirm({

+ 7 - 5
app/src/views/domain/components/RightSettings.vue

@@ -9,14 +9,16 @@ import ChatGPT from '@/components/ChatGPT/ChatGPT.vue'
 import { formatDateTime } from '@/lib/helper'
 import Deploy from '@/views/domain/components/Deploy.vue'
 import { useSettingsStore } from '@/pinia'
+import type { ChatComplicationMessage } from '@/api/openai'
+import type { NgxConfig } from '@/api/ngx'
 
 const settings = useSettingsStore()
 const { $gettext } = useGettext()
-const configText = inject('configText')
-const ngx_config = inject('ngx_config')
-const enabled = inject('enabled')
-const name = inject('name')
-const history_chatgpt_record = inject('history_chatgpt_record')
+const configText = inject('configText') as Ref<string>
+const ngx_config = inject('ngx_config') as Ref<NgxConfig>
+const enabled = inject('enabled') as Ref<boolean>
+const name = inject('name') as Ref<string>
+const history_chatgpt_record = inject('history_chatgpt_record') as Ref<ChatComplicationMessage[]>
 const filename = inject('filename')
 const data: Ref<Site> = inject('data') as Ref<Site>
 

+ 7 - 3
app/src/views/domain/components/SiteDuplicate.vue

@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import { computed, nextTick, reactive, ref, watch } from 'vue'
 import { useGettext } from 'vue3-gettext'
 import { Form, message, notification } from 'ant-design-vue'
 import gettext from '@/gettext'
@@ -27,7 +26,12 @@ const show = computed({
   },
 })
 
-const modelRef = reactive({ name: '', target: [] })
+interface Model {
+  name: string // site name
+  target: number[] // ids of deploy targets
+}
+
+const modelRef: Model = reactive({ name: '', target: [] })
 
 const rulesRef = reactive({
   name: [
@@ -49,7 +53,7 @@ const { validate, validateInfos, clearValidate } = Form.useForm(modelRef, rulesR
 
 const loading = ref(false)
 
-const node_map = reactive({})
+const node_map: Record<number, string> = reactive({})
 
 function onSubmit() {
   validate().then(async () => {

+ 2 - 2
app/src/views/domain/ngx_conf/LocationEditor.vue

@@ -33,13 +33,13 @@ function add() {
 
 function save() {
   adding.value = false
-  ngx_config.servers[props.currentServerIndex].locations?.push({
+  ngx_config.servers[props.currentServerIndex!].locations?.push({
     ...location,
   })
 }
 
 function remove(index: number) {
-  ngx_config.servers[props.currentServerIndex].locations?.splice(index, 1)
+  ngx_config.servers[props.currentServerIndex!].locations?.splice(index, 1)
 }
 </script>
 

+ 2 - 2
app/src/views/domain/ngx_conf/LogEntry.vue

@@ -17,7 +17,7 @@ const errorIdx = ref()
 
 const hasAccessLog = computed(() => {
   let flag = false
-  props.ngxConfig.servers[props.currentServerIdx].directives.forEach((v, k) => {
+  props.ngxConfig?.servers[props.currentServerIdx].directives?.forEach((v, k) => {
     if (v.directive === 'access_log') {
       flag = true
       accessIdx.value = k
@@ -29,7 +29,7 @@ const hasAccessLog = computed(() => {
 
 const hasErrorLog = computed(() => {
   let flag = false
-  props.ngxConfig.servers[props.currentServerIdx].directives.forEach((v, k) => {
+  props.ngxConfig?.servers[props.currentServerIdx].directives?.forEach((v, k) => {
     if (v.directive === 'error_log') {
       flag = true
       errorIdx.value = k

+ 13 - 12
app/src/views/domain/ngx_conf/NgxConfigEditor.vue

@@ -27,7 +27,7 @@ const emit = defineEmits(['callback', 'update:auto_cert'])
 
 const { $gettext } = useGettext()
 
-const save_site_config = inject('save_site_config')!
+const save_site_config = inject('save_site_config') as () => Promise<void>
 
 const route = useRoute()
 
@@ -68,7 +68,7 @@ const current_server_directives = computed(() => {
 })
 
 const directivesMap: ComputedRef<Record<string, NgxDirective[]>> = computed(() => {
-  const map = {}
+  const map: Record<string, NgxDirective[]> = {}
 
   current_server_directives.value?.forEach((v, k) => {
     v.idx = k
@@ -81,6 +81,7 @@ const directivesMap: ComputedRef<Record<string, NgxDirective[]>> = computed(() =
   return map
 })
 
+// eslint-disable-next-line sonarjs/cognitive-complexity
 function change_tls(status: boolean) {
   if (status) {
     // deep copy servers[0] to servers[1]
@@ -93,15 +94,15 @@ function change_tls(status: boolean) {
     const servers = ngx_config.servers
 
     let i = 0
-    while (i < servers[1].directives.length) {
-      const v = servers[1].directives[i]
-      if (v.directive === 'listen')
-        servers[1].directives.splice(i, 1)
+    while (i < (servers?.[1].directives?.length ?? 0)) {
+      const v = servers?.[1]?.directives?.[i]
+      if (v?.directive === 'listen')
+        servers[1]?.directives?.splice(i, 1)
       else
         i++
     }
 
-    servers[1].directives.splice(0, 0, {
+    servers?.[1]?.directives?.splice(0, 0, {
       directive: 'listen',
       params: '443 ssl',
     }, {
@@ -112,10 +113,10 @@ function change_tls(status: boolean) {
       params: 'on',
     })
 
-    const server_name = directivesMap.value.server_name[0]
+    const server_name_idx = directivesMap.value?.server_name?.[0].idx ?? 0
 
     if (!directivesMap.value.ssl_certificate) {
-      servers[1].directives.splice(server_name.idx + 1, 0, {
+      servers?.[1]?.directives?.splice(server_name_idx + 1, 0, {
         directive: 'ssl_certificate',
         params: '',
       })
@@ -123,7 +124,7 @@ function change_tls(status: boolean) {
 
     setTimeout(() => {
       if (!directivesMap.value.ssl_certificate_key) {
-        servers[1].directives.splice(server_name.idx + 2, 0, {
+        servers?.[1]?.directives?.splice(server_name_idx + 2, 0, {
           directive: 'ssl_certificate_key',
           params: '',
         })
@@ -144,8 +145,8 @@ const support_ssl = computed(() => {
   const servers = ngx_config.servers
   for (const server_key in servers) {
     for (const k in servers[server_key].directives) {
-      const v = servers[server_key].directives[k]
-      if (v.directive === 'listen' && v.params.indexOf('ssl') > 0)
+      const v = servers?.[server_key]?.directives?.[Number.parseInt(k)]
+      if (v?.directive === 'listen' && v?.params?.indexOf('ssl') > 0)
         return true
     }
   }

+ 2 - 2
app/src/views/domain/ngx_conf/config_template/ConfigTemplate.vue

@@ -52,10 +52,10 @@ async function add() {
   ngx_config.custom = ngx_config.custom?.trim()
 
   if (data.value.locations)
-    ngx_config.servers[props.currentServerIndex].locations.push(...data.value.locations)
+    ngx_config?.servers?.[props.currentServerIndex]?.locations?.push(...data.value.locations)
 
   if (data.value.directives)
-    ngx_config.servers[props.currentServerIndex].directives.push(...data.value.directives)
+    ngx_config?.servers?.[props.currentServerIndex]?.directives?.push(...data.value.directives)
 
   visible.value = false
 }

+ 3 - 3
app/src/views/domain/ngx_conf/config_template/TemplateForm.vue

@@ -9,9 +9,9 @@ const props = defineProps<{
 }>()
 
 const emit = defineEmits<{
-  'update:data': (data: {
+  'update:data': [data: {
     [key: string]: Variable
-  }) => void
+  }]
 }>()
 
 const data = computed({
@@ -30,7 +30,7 @@ const data = computed({
       v-for="(_, k) in data"
       :key="k"
       v-model:data="data[k]"
-      :name="k"
+      :name="k.toString()"
     />
   </AForm>
 </template>

+ 1 - 1
app/src/views/domain/ngx_conf/config_template/TemplateFormItem.vue

@@ -10,7 +10,7 @@ const props = defineProps<{
 }>()
 
 const emit = defineEmits<{
-  'update:data': (data: Variable) => void
+  'update:data': [data: Variable]
 }>()
 
 const data = computed({

+ 2 - 1
app/src/views/domain/ngx_conf/directive/DirectiveAdd.vue

@@ -3,6 +3,7 @@ import { reactive, ref } from 'vue'
 import { useGettext } from 'vue3-gettext'
 import { DeleteOutlined } from '@ant-design/icons-vue'
 import CodeEditor from '@/components/CodeEditor'
+import type { NgxDirective } from '@/api/ngx'
 
 const props = defineProps<{
   idx?: number
@@ -12,7 +13,7 @@ const emit = defineEmits(['save'])
 
 const { $gettext } = useGettext()
 
-const ngx_directives = inject('ngx_directives')
+const ngx_directives = inject('ngx_directives') as NgxDirective[]
 const directive = reactive({ directive: '', params: '' })
 const adding = ref(false)
 const mode = ref('default')

+ 2 - 2
app/src/views/domain/ngx_conf/directive/DirectiveEditorItem.vue

@@ -26,7 +26,7 @@ const content = ref('')
 function init() {
   if (ngx_directives[props.index].directive === 'include') {
     config.get(ngx_directives[props.index].params).then(r => {
-      content.value = r.config
+      content.value = r.content
     })
   }
 }
@@ -35,7 +35,7 @@ watch(props, init)
 
 function save() {
   config.save(ngx_directives[props.index].params, { content: content.value }).then(r => {
-    content.value = r.config
+    content.value = r.content
     message.success($gettext('Saved successfully'))
   }).catch(r => {
     message.error(interpolate($gettext('Save error %{msg}'), { msg: r.message ?? '' }))

+ 11 - 8
app/src/views/environment/Environment.vue

@@ -7,13 +7,14 @@ import { datetime } from '@/components/StdDesign/StdDataDisplay/StdTableTransfor
 import environment from '@/api/environment'
 import StdCurd from '@/components/StdDesign/StdDataDisplay/StdCurd.vue'
 import { input } from '@/components/StdDesign/StdDataEntry'
+import type { Column } from '@/components/StdDesign/types'
 
 const { $gettext } = useGettext()
 
-const columns = [{
+const columns: Column[] = [{
   title: () => $gettext('Name'),
   dataIndex: 'name',
-  sorter: true,
+  sortable: true,
   pithy: true,
   edit: {
     type: input,
@@ -22,18 +23,20 @@ const columns = [{
 {
   title: () => $gettext('URL'),
   dataIndex: 'url',
-  sorter: true,
+  sortable: true,
   pithy: true,
   edit: {
     type: input,
-    placeholder: () => 'https://10.0.0.1:9000',
+    config: {
+      placeholder: () => 'https://10.0.0.1:9000',
+    },
   },
 },
 {
   title: () => 'NodeSecret',
   dataIndex: 'token',
-  sorter: true,
-  display: false,
+  sortable: true,
+  hidden: true,
   edit: {
     type: input,
   },
@@ -88,14 +91,14 @@ const columns = [{
 
     return h('div', template)
   },
-  sorter: true,
+  sortable: true,
   pithy: true,
 },
 {
   title: () => $gettext('Updated at'),
   dataIndex: 'updated_at',
   customRender: datetime,
-  sorter: true,
+  sortable: true,
   pithy: true,
 },
 {

+ 4 - 8
app/src/views/nginx_log/NginxLog.vue

@@ -1,7 +1,5 @@
 <script setup lang="ts">
 import { useGettext } from 'vue3-gettext'
-import type { Ref, UnwrapNestedRefs } from 'vue'
-import { computed, nextTick, onMounted, onUnmounted, reactive, ref, watch } from 'vue'
 import type ReconnectingWebSocket from 'reconnecting-websocket'
 import { useRoute, useRouter } from 'vue-router'
 import { debounce } from 'lodash'
@@ -11,7 +9,7 @@ import nginx_log from '@/api/nginx_log'
 import ws from '@/lib/websocket'
 
 const { $gettext } = useGettext()
-const logContainer: Ref<Element> = ref()!
+const logContainer = ref()
 let websocket: ReconnectingWebSocket | WebSocket
 const route = useRoute()
 const buffer = ref('')
@@ -21,7 +19,7 @@ const router = useRouter()
 const loading = ref(false)
 const filter = ref('')
 
-const control: UnwrapNestedRefs<INginxLogData> = reactive({
+const control: INginxLogData = reactive({
   type: logType(),
   conf_name: route.query.conf_name as string,
   server_idx: Number.parseInt(route.query.server_idx as string),
@@ -53,10 +51,8 @@ function addLog(data: string, prepend: boolean = false) {
     buffer.value += data
 
   nextTick(() => {
-    const elem = (logContainer.value as Element)
-
-    elem?.scroll({
-      top: elem.scrollHeight,
+    logContainer.value?.scroll({
+      top: logContainer.value.scrollHeight,
       left: 0,
     })
   })

+ 12 - 1
app/src/views/other/Error.vue

@@ -2,6 +2,17 @@
 import { useGettext } from 'vue3-gettext'
 
 const { $gettext } = useGettext()
+
+const route = useRoute()
+
+const info = computed(() => {
+  if (typeof route.meta.error === 'function')
+    return route.meta.error()
+  else if (typeof route.meta.error === 'string')
+    return route.meta.error
+  else
+    return $gettext('File Not Found')
+})
 </script>
 
 <template>
@@ -9,7 +20,7 @@ const { $gettext } = useGettext()
     <h1 class="title">
       {{ $route.meta.status_code || 404 }}
     </h1>
-    <p>{{ $route.meta.error?.() ?? $gettext('File Not Found') }}</p>
+    <p>{{ info }}</p>
     <AButton
       type="primary"
       @click="$router.push('/')"

+ 1 - 1
app/src/views/other/Login.vue

@@ -95,7 +95,7 @@ const loginWithCasdoor = () => {
 
 if (route.query?.code !== undefined && route.query?.state !== undefined) {
   loading.value = true
-  auth.casdoor_login(route.query.code.toString(), route.query.state.toString()).then(async () => {
+  auth.casdoor_login(route.query?.code?.toString(), route.query?.state?.toString()).then(async () => {
     message.success($gettext('Login successful'), 1)
 
     const next = (route.query?.next || '').toString() || '/'

+ 3 - 3
app/src/views/pty/Terminal.vue

@@ -10,7 +10,7 @@ import ws from '@/lib/websocket'
 const { $gettext } = useGettext()
 
 let term: Terminal | null
-let ping: NodeJS.Timer | null
+let ping: number
 
 const websocket = ws('/api/pty')
 
@@ -83,9 +83,9 @@ function wsOnOpen() {
 
 onUnmounted(() => {
   window.removeEventListener('resize', fit)
-  clearInterval(ping as number)
+  clearInterval(ping)
   term?.dispose()
-  ping = null
+  ping = 0
   websocket.close()
 })
 

+ 8 - 17
app/src/views/system/Upgrade.vue

@@ -8,22 +8,13 @@ import { message } from 'ant-design-vue'
 import { useRoute } from 'vue-router'
 import websocket from '@/lib/websocket'
 import version from '@/version.json'
+import type { RuntimeInfo } from '@/api/upgrade'
 import upgrade from '@/api/upgrade'
 
 const { $gettext } = useGettext()
 
 const route = useRoute()
-
-interface APIData {
-  name: string
-  os: string
-  arch: string
-  ex_path: string
-  body: string
-  published_at: string
-}
-
-const data: Ref<APIData> = ref({})
+const data = ref({}) as Ref<RuntimeInfo>
 const last_check = ref('')
 const loading = ref(false)
 const channel = ref('stable')
@@ -65,16 +56,16 @@ const is_latest_ver = computed(() => {
   return data.value.name === `v${version.version}`
 })
 
-const logContainer = ref(null)
+const logContainer = ref()
 
 function log(msg: string) {
   const para = document.createElement('p')
 
-  para.appendChild(document.createTextNode($gettext(msg)));
+  para.appendChild(document.createTextNode($gettext(msg)))
 
-  (logContainer.value as Element).appendChild(para);
+  logContainer.value.appendChild(para)
 
-  (logContainer.value as Element).scroll({ top: 320, left: 0, behavior: 'smooth' })
+  logContainer.value.scroll({ top: 320, left: 0, behavior: 'smooth' })
 }
 
 const dry_run = computed(() => {
@@ -85,8 +76,8 @@ async function perform_upgrade() {
   progressStatus.value = 'active'
   modalClosable.value = false
   modalVisible.value = true
-  progressPercent.value = 0;
-  (logContainer.value as Element).innerHTML = ''
+  progressPercent.value = 0
+  logContainer.value.innerHTML = ''
 
   log($gettext('Upgrading Nginx UI, please wait...'))
 

+ 1 - 1
app/version.json

@@ -1 +1 @@
-{"version":"2.0.0-beta.4","build_id":56,"total_build":260}
+{"version":"2.0.0-beta.4","build_id":62,"total_build":266}

+ 13 - 12
internal/analytic/analytic.go

@@ -6,18 +6,18 @@ import (
 	"time"
 )
 
-type Usage struct {
-	Time  time.Time   `json:"x"`
-	Usage interface{} `json:"y"`
+type Usage[T uint64 | float64] struct {
+	Time  time.Time `json:"x"`
+	Usage T         `json:"y"`
 }
 
 var (
-	CpuUserRecord   []Usage
-	CpuTotalRecord  []Usage
-	NetRecvRecord   []Usage
-	NetSentRecord   []Usage
-	DiskWriteRecord []Usage
-	DiskReadRecord  []Usage
+	CpuUserRecord   []Usage[float64]
+	CpuTotalRecord  []Usage[float64]
+	NetRecvRecord   []Usage[uint64]
+	NetSentRecord   []Usage[uint64]
+	DiskWriteRecord []Usage[uint64]
+	DiskReadRecord  []Usage[uint64]
 	LastDiskWrites  uint64
 	LastDiskReads   uint64
 	LastNetSent     uint64
@@ -37,9 +37,10 @@ func init() {
 	now := time.Now()
 	// init record slices
 	for i := 100; i > 0; i-- {
-		u := Usage{Time: now.Add(time.Duration(-i) * time.Second), Usage: 0}
-		CpuUserRecord = append(CpuUserRecord, u)
-		CpuTotalRecord = append(CpuTotalRecord, u)
+		uf := Usage[float64]{Time: now.Add(time.Duration(-i) * time.Second), Usage: 0}
+		CpuUserRecord = append(CpuUserRecord, uf)
+		CpuTotalRecord = append(CpuTotalRecord, uf)
+		u := Usage[uint64]{Time: now.Add(time.Duration(-i) * time.Second), Usage: 0}
 		NetRecvRecord = append(NetRecvRecord, u)
 		NetSentRecord = append(NetSentRecord, u)
 		DiskWriteRecord = append(DiskWriteRecord, u)

+ 6 - 6
internal/analytic/record.go

@@ -41,14 +41,14 @@ func recordCpu(now time.Time) {
 	cpuSystemUsage := (cpuTimesAfter[0].System - cpuTimesBefore[0].System) / (float64(1000*threadNum) / 1000)
 	cpuSystemUsage *= 100
 
-	u := Usage{
+	u := Usage[float64]{
 		Time:  now,
 		Usage: cpuUserUsage,
 	}
 
 	CpuUserRecord = append(CpuUserRecord, u)
 
-	s := Usage{
+	s := Usage[float64]{
 		Time:  now,
 		Usage: cpuUserUsage + cpuSystemUsage,
 	}
@@ -75,11 +75,11 @@ func recordNetwork(now time.Time) {
 	if len(network) == 0 {
 		return
 	}
-	NetRecvRecord = append(NetRecvRecord, Usage{
+	NetRecvRecord = append(NetRecvRecord, Usage[uint64]{
 		Time:  now,
 		Usage: network[0].BytesRecv - LastNetRecv,
 	})
-	NetSentRecord = append(NetSentRecord, Usage{
+	NetSentRecord = append(NetSentRecord, Usage[uint64]{
 		Time:  now,
 		Usage: network[0].BytesSent - LastNetSent,
 	})
@@ -96,11 +96,11 @@ func recordNetwork(now time.Time) {
 func recordDiskIO(now time.Time) {
 	readCount, writeCount := getTotalDiskIO()
 
-	DiskReadRecord = append(DiskReadRecord, Usage{
+	DiskReadRecord = append(DiskReadRecord, Usage[uint64]{
 		Time:  now,
 		Usage: readCount - LastDiskReads,
 	})
-	DiskWriteRecord = append(DiskWriteRecord, Usage{
+	DiskWriteRecord = append(DiskWriteRecord, Usage[uint64]{
 		Time:  now,
 		Usage: writeCount - LastDiskWrites,
 	})

+ 5 - 5
internal/analytic/stat.go

@@ -23,11 +23,11 @@ type MemStat struct {
 }
 
 type DiskStat struct {
-	Total      string  `json:"total"`
-	Used       string  `json:"used"`
-	Percentage float64 `json:"percentage"`
-	Writes     Usage   `json:"writes"`
-	Reads      Usage   `json:"reads"`
+	Total      string        `json:"total"`
+	Used       string        `json:"used"`
+	Percentage float64       `json:"percentage"`
+	Writes     Usage[uint64] `json:"writes"`
+	Reads      Usage[uint64] `json:"reads"`
 }
 
 func GetMemoryStat() (MemStat, error) {