fix: resolved all vue-tsc errors

This commit is contained in:
0xJacky 2023-11-29 22:04:30 +08:00
parent d325dd7493
commit ab1adcfa3d
No known key found for this signature in database
GPG key ID: B6E4A6E4A561BAF0
64 changed files with 675 additions and 451 deletions

View file

@ -2,7 +2,7 @@ package analytic
import ( import (
"fmt" "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/0xJacky/Nginx-UI/internal/logger"
"github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/cpu"
"github.com/shirou/gopsutil/v3/host" "github.com/shirou/gopsutil/v3/host"
@ -17,22 +17,6 @@ import (
"github.com/gorilla/websocket" "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) { func Analytic(c *gin.Context) {
var upGrader = websocket.Upgrader{ var upGrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { CheckOrigin: func(r *http.Request) bool {
@ -51,7 +35,7 @@ func Analytic(c *gin.Context) {
var stat Stat var stat Stat
for { for {
stat.Memory, err = analytic2.GetMemoryStat() stat.Memory, err = analytic.GetMemoryStat()
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
@ -76,7 +60,7 @@ func Analytic(c *gin.Context) {
stat.LoadAvg, _ = load.Avg() stat.LoadAvg, _ = load.Avg()
stat.Disk, err = analytic2.GetDiskStat() stat.Disk, err = analytic.GetDiskStat()
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
@ -103,20 +87,24 @@ func Analytic(c *gin.Context) {
} }
func GetAnalyticInit(c *gin.Context) { func GetAnalyticInit(c *gin.Context) {
cpuInfo, _ := cpu.Info() cpuInfo, err := cpu.Info()
network, _ := net.IOCounters(false)
memory, err := analytic2.GetMemoryStat()
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return
} }
diskStat, err := analytic2.GetDiskStat() network, err := net.IOCounters(false)
if err != nil {
logger.Error(err)
}
memory, err := analytic.GetMemoryStat()
if err != nil {
logger.Error(err)
}
diskStat, err := analytic.GetDiskStat()
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
return
} }
var _net net.IOCountersStat var _net net.IOCountersStat
@ -132,86 +120,30 @@ func GetAnalyticInit(c *gin.Context) {
hostInfo.Platform = "CentOS" hostInfo.Platform = "CentOS"
} }
loadAvg, _ := load.Avg() loadAvg, err := load.Avg()
c.JSON(http.StatusOK, gin.H{ if err != nil {
"host": hostInfo, logger.Error(err)
"cpu": gin.H{ }
"info": cpuInfo,
"user": analytic2.CpuUserRecord, c.JSON(http.StatusOK, InitResp{
"total": analytic2.CpuTotalRecord, Host: hostInfo,
CPU: CPURecords{
Info: cpuInfo,
User: analytic.CpuUserRecord,
Total: analytic.CpuTotalRecord,
}, },
"network": gin.H{ Network: NetworkRecords{
"init": _net, Init: _net,
"bytesRecv": analytic2.NetRecvRecord, BytesRecv: analytic.NetRecvRecord,
"bytesSent": analytic2.NetSentRecord, BytesSent: analytic.NetSentRecord,
}, },
"disk_io": gin.H{ DiskIO: DiskIORecords{
"writes": analytic2.DiskWriteRecord, Writes: analytic.DiskWriteRecord,
"reads": analytic2.DiskReadRecord, Reads: analytic.DiskReadRecord,
}, },
"memory": memory, Memory: memory,
"disk": diskStat, Disk: diskStat,
"loadavg": loadAvg, LoadAvg: loadAvg,
}) })
} }
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
},
}
// 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)
}
}

70
api/analytic/nodes.go Normal file
View file

@ -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
api/analytic/type.go Normal file
View file

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

View file

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

View file

@ -1,9 +1,120 @@
import http from '@/lib/http' 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 = { const analytic = {
init() { init(): Promise<AnalyticInit> {
return http.get('/analytic/init') return http.get('/analytic/init')
}, },
server() {
return ws('/api/analytic')
},
nodes() {
return ws('/api/analytic/nodes')
},
} }
export default analytic export default analytic

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import type { SelectProps } from 'ant-design-vue' 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 auto_cert from '@/api/auto_cert'
const { $gettext } = useGettext() 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(() => { const code = computed(() => {
return data.code return data.code
@ -14,9 +18,11 @@ const code = computed(() => {
const provider_idx = ref() const provider_idx = ref()
function init() { function init() {
data.configuration = { if (!data.configuration) {
credentials: {}, data.configuration = {
additional: {}, credentials: {},
additional: {},
}
} }
providers.value?.forEach((v: { code: string }, k: number) => { providers.value?.forEach((v: { code: string }, k: number) => {
if (v.code === code.value) if (v.code === code.value)
@ -39,6 +45,7 @@ watch(code, init)
watch(current, () => { watch(current, () => {
data.code = current.value.code data.code = current.value.code
data.provider = current.value.name data.provider = current.value.name
auto_cert.get_dns_provider(current.value.code).then(r => { auto_cert.get_dns_provider(current.value.code).then(r => {
Object.assign(current.value, r) Object.assign(current.value, r)
}) })
@ -47,7 +54,7 @@ watch(current, () => {
const options = computed<SelectProps['options']>(() => { const options = computed<SelectProps['options']>(() => {
const list: SelectProps['options'] = [] const list: SelectProps['options'] = []
providers.value.forEach((v: { name: string }, k: number) => { providers.value.forEach((v: DNSProvider, k: number) => {
list!.push({ list!.push({
value: k, value: k,
label: v.name, label: v.name,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,14 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import type { SelectProps } from 'ant-design-vue' 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 auto_cert from '@/api/auto_cert'
import dns_credential from '@/api/dns_credential' import dns_credential from '@/api/dns_credential'
const { $gettext } = useGettext() const { $gettext } = useGettext()
const providers = ref([]) const providers = ref([]) as Ref<DNSProvider[]>
const credentials = ref([]) 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(() => { const code = computed(() => {
return data.code return data.code
@ -16,7 +22,7 @@ const code = computed(() => {
const provider_idx = ref() const provider_idx = ref()
function init() { function init() {
providers.value?.forEach((v, k: number) => { providers.value?.forEach((v: DNSProvider, k: number) => {
if (v.code === code.value) if (v.code === code.value)
provider_idx.value = k provider_idx.value = k
}) })
@ -42,7 +48,7 @@ watch(current, () => {
dns_credential.get_list({ provider: data.provider }).then(r => { dns_credential.get_list({ provider: data.provider }).then(r => {
r.data.forEach(v => { r.data.forEach(v => {
credentials.value.push({ credentials.value?.push({
value: v.id, value: v.id,
label: v.name, label: v.name,
}) })

View file

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

View file

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

View file

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

View file

@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, reactive, ref, watch } from 'vue'
import { useGettext } from 'vue3-gettext' import { useGettext } from 'vue3-gettext'
import { Form, message, notification } from 'ant-design-vue' import { Form, message, notification } from 'ant-design-vue'
import gettext from '@/gettext' 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({ const rulesRef = reactive({
name: [ name: [
@ -49,7 +53,7 @@ const { validate, validateInfos, clearValidate } = Form.useForm(modelRef, rulesR
const loading = ref(false) const loading = ref(false)
const node_map = reactive({}) const node_map: Record<number, string> = reactive({})
function onSubmit() { function onSubmit() {
validate().then(async () => { validate().then(async () => {

View file

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

View file

@ -17,7 +17,7 @@ const errorIdx = ref()
const hasAccessLog = computed(() => { const hasAccessLog = computed(() => {
let flag = false 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') { if (v.directive === 'access_log') {
flag = true flag = true
accessIdx.value = k accessIdx.value = k
@ -29,7 +29,7 @@ const hasAccessLog = computed(() => {
const hasErrorLog = computed(() => { const hasErrorLog = computed(() => {
let flag = false 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') { if (v.directive === 'error_log') {
flag = true flag = true
errorIdx.value = k errorIdx.value = k

View file

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

View file

@ -52,10 +52,10 @@ async function add() {
ngx_config.custom = ngx_config.custom?.trim() ngx_config.custom = ngx_config.custom?.trim()
if (data.value.locations) 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) 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 visible.value = false
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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