- 公网反代:Nginx 状态卡片(版本 / 运行状态 / 配置校验 / 端口监听) - 部署设置第一、二梯队(HTTPS 开关、安全头、限流、连接数、仅中国大陆 IP) - 隧道检测兼容 frps v0.68 / v0.71(V2 与旧版端点) - 恢复被清空的 internal/frp/store.go、internal/api/ddns_handler.go、web/src/views/DdnsView.vue
104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
export function formatBytes(bytes: number): string {
|
|
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B'
|
|
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
let value = bytes
|
|
let unit = 0
|
|
while (value >= 1024 && unit < units.length - 1) {
|
|
value /= 1024
|
|
unit++
|
|
}
|
|
const digits = value >= 100 || unit === 0 ? 0 : value >= 10 ? 1 : 2
|
|
return `${value.toFixed(digits)} ${units[unit]}`
|
|
}
|
|
|
|
export function formatRate(bytesPerSec: number): string {
|
|
if (!Number.isFinite(bytesPerSec) || bytesPerSec <= 0) return '0 B/s'
|
|
return `${formatBytes(bytesPerSec)}/s`
|
|
}
|
|
|
|
export function formatUptime(seconds: number): string {
|
|
const h = Math.floor(seconds / 3600)
|
|
const m = Math.floor((seconds % 3600) / 60)
|
|
if (h > 0) return `${h} 小时 ${m} 分钟`
|
|
return `${m} 分钟`
|
|
}
|
|
|
|
export function formatRelativeTime(iso?: string): string {
|
|
if (!iso) return '-'
|
|
const date = new Date(iso)
|
|
if (Number.isNaN(date.getTime())) return iso
|
|
const diff = Date.now() - date.getTime()
|
|
const minutes = Math.floor(diff / 60000)
|
|
if (minutes < 1) return '刚刚'
|
|
if (minutes < 60) return `${minutes} 分钟前`
|
|
const hours = Math.floor(minutes / 60)
|
|
if (hours < 24) return `${hours} 小时前`
|
|
const days = Math.floor(hours / 24)
|
|
return `${days} 天前`
|
|
}
|
|
|
|
export function formatMs(seconds: number): string {
|
|
const ms = Math.round(seconds * 1000)
|
|
return `${ms} ms`
|
|
}
|
|
|
|
export function formatDate(iso?: string): string {
|
|
if (!iso) return '-'
|
|
const date = new Date(iso)
|
|
if (Number.isNaN(date.getTime())) return iso
|
|
return date.toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
})
|
|
}
|
|
|
|
/** Normalize log timestamps to YYYY-MM-DD HH:mm:ss */
|
|
export function formatLogTime(value?: string): string {
|
|
if (!value) return '-'
|
|
|
|
const trimmed = value.trim()
|
|
|
|
const standard = trimmed.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/)
|
|
if (standard) return standard[1]
|
|
|
|
const iso = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})/)
|
|
if (iso) return `${iso[1]}-${iso[2]}-${iso[3]} ${iso[4]}:${iso[5]}:${iso[6]}`
|
|
|
|
const nginx = trimmed.match(/^(\d{4})\/(\d{2})\/(\d{2}) (\d{2}):(\d{2}):(\d{2})/)
|
|
if (nginx) return `${nginx[1]}-${nginx[2]}-${nginx[3]} ${nginx[4]}:${nginx[5]}:${nginx[6]}`
|
|
|
|
const date = new Date(trimmed)
|
|
if (!Number.isNaN(date.getTime())) {
|
|
const pad = (n: number) => String(n).padStart(2, '0')
|
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
|
|
}
|
|
|
|
return trimmed
|
|
}
|
|
|
|
/** Normalize leading timestamp in a raw log line */
|
|
export function formatLogLine(line: string): string {
|
|
const trimmed = line.trim()
|
|
if (!trimmed) return line
|
|
|
|
const nginx = trimmed.match(/^(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})(.*)$/)
|
|
if (nginx) return formatLogTime(nginx[1]) + nginx[2]
|
|
|
|
const iso = trimmed.match(/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:[+-]\d{2}:\d{2}|Z)?)(.*)$/)
|
|
if (iso) return formatLogTime(iso[1]) + iso[2]
|
|
|
|
if (trimmed.startsWith('{')) {
|
|
try {
|
|
const raw = JSON.parse(trimmed) as { time?: string }
|
|
if (raw.time) return trimmed.replace(raw.time, formatLogTime(raw.time))
|
|
} catch {
|
|
// not JSON
|
|
}
|
|
}
|
|
|
|
return trimmed
|
|
}
|