nezha-dash-v1/src/lib/utils.ts
2024-12-19 14:23:28 +08:00

326 lines
9.6 KiB
TypeScript

import { NezhaServer } from "@/types/nezha-api"
import { type ClassValue, clsx } from "clsx"
import dayjs from "dayjs"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export function formatNezhaInfo(now: number, serverInfo: NezhaServer) {
const lastActiveTime = serverInfo.last_active.startsWith("000")
? 0
: parseISOTimestamp(serverInfo.last_active)
return {
...serverInfo,
cpu: serverInfo.state.cpu || 0,
gpu: serverInfo.state.gpu || [],
process: serverInfo.state.process_count || 0,
up: serverInfo.state.net_out_speed / 1024 / 1024 || 0,
down: serverInfo.state.net_in_speed / 1024 / 1024 || 0,
last_active_time_string: lastActiveTime ? new Date(lastActiveTime).toLocaleString() : "",
online: now - lastActiveTime <= 30000,
uptime: serverInfo.state.uptime || 0,
version: serverInfo.host.version || null,
tcp: serverInfo.state.tcp_conn_count || 0,
udp: serverInfo.state.udp_conn_count || 0,
mem: (serverInfo.state.mem_used / serverInfo.host.mem_total) * 100 || 0,
swap: (serverInfo.state.swap_used / serverInfo.host.swap_total) * 100 || 0,
disk: (serverInfo.state.disk_used / serverInfo.host.disk_total) * 100 || 0,
stg: (serverInfo.state.disk_used / serverInfo.host.disk_total) * 100 || 0,
country_code: serverInfo.country_code,
platform: serverInfo.host.platform || "",
net_out_transfer: serverInfo.state.net_out_transfer || 0,
net_in_transfer: serverInfo.state.net_in_transfer || 0,
arch: serverInfo.host.arch || "",
mem_total: serverInfo.host.mem_total || 0,
swap_total: serverInfo.host.swap_total || 0,
disk_total: serverInfo.host.disk_total || 0,
boot_time: serverInfo.host.boot_time || 0,
platform_version: serverInfo.host.platform_version || "",
cpu_info: serverInfo.host.cpu || [],
gpu_info: serverInfo.host.gpu || [],
load_1: serverInfo.state.load_1?.toFixed(2) || 0.0,
load_5: serverInfo.state.load_5?.toFixed(2) || 0.0,
load_15: serverInfo.state.load_15?.toFixed(2) || 0.0,
public_note: handlePublicNote(serverInfo.id, serverInfo.public_note || ""),
}
}
export function getDaysBetweenDatesWithAutoRenewal({
autoRenewal,
cycle,
startDate,
endDate,
}: BillingData): { days: number; cycleLabel: string; remainingPercentage: number } {
let months = 1
// 套餐资费
let cycleLabel = cycle
switch (cycle.toLowerCase()) {
case "月":
case "m":
case "mo":
case "month":
case "monthly":
cycleLabel = "月"
months = 1
break
case "年":
case "y":
case "yr":
case "year":
case "annual":
cycleLabel = "年"
months = 12
break
case "季":
case "quarterly":
cycleLabel = "季"
months = 3
break
case "半":
case "半年":
case "h":
case "half":
case "semi-annually":
cycleLabel = "半年"
months = 6
break
default:
cycleLabel = cycle
break
}
const nowTime = new Date().getTime()
const endTime = dayjs(endDate).valueOf()
if (autoRenewal !== "1") {
return {
days: getDaysBetweenDates(endDate, new Date(nowTime).toISOString()),
cycleLabel: cycleLabel,
remainingPercentage:
getDaysBetweenDates(endDate, new Date(nowTime).toISOString()) /
dayjs(endDate).diff(startDate, "day") >
1
? 1
: getDaysBetweenDates(endDate, new Date(nowTime).toISOString()) /
dayjs(endDate).diff(startDate, "day"),
}
}
if (nowTime < endTime) {
return {
days: getDaysBetweenDates(endDate, new Date(nowTime).toISOString()),
cycleLabel: cycleLabel,
remainingPercentage:
getDaysBetweenDates(endDate, new Date(nowTime).toISOString()) / (30 * months) > 1
? 1
: getDaysBetweenDates(endDate, new Date(nowTime).toISOString()) / (30 * months),
}
}
const nextTime = getNextCycleTime(endTime, months, nowTime)
const diff = dayjs(nextTime).diff(dayjs(), "day") + 1
const remainingPercentage = diff / (30 * months) > 1 ? 1 : diff / (30 * months)
return {
days: diff,
cycleLabel: cycleLabel,
remainingPercentage: remainingPercentage,
}
}
// Thanks to hi2shark for the code
// https://github.com/hi2shark/nazhua/blob/main/src/utils/date.js#L86
export function getNextCycleTime(startDate: number, months: number, specifiedDate: number): number {
const start = dayjs(startDate)
const checkDate = dayjs(specifiedDate)
if (!start.isValid() || months <= 0) {
throw new Error("参数无效:请检查起始日期、周期月份数和指定日期。")
}
let nextDate = start
// 循环增加周期直到大于当前日期
let whileStatus = true
while (whileStatus) {
nextDate = nextDate.add(months, "month")
whileStatus = nextDate.valueOf() <= checkDate.valueOf()
}
return nextDate.valueOf() // 返回时间毫秒数
}
export function getDaysBetweenDates(date1: string, date2: string): number {
const oneDay = 24 * 60 * 60 * 1000 // 一天的毫秒数
const firstDate = new Date(date1)
const secondDate = new Date(date2)
// 计算两个日期之间的天数差异
return Math.round((firstDate.getTime() - secondDate.getTime()) / oneDay)
}
export const fetcher = (url: string) =>
fetch(url)
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText)
}
return res.json()
})
.then((data) => data.data)
.catch((err) => {
console.error(err)
throw err
})
export const nezhaFetcher = async (url: string) => {
const res = await fetch(url)
if (!res.ok) {
const error = new Error("An error occurred while fetching the data.")
// @ts-expect-error - res.json() returns a Promise<any>
error.info = await res.json()
// @ts-expect-error - res.status is a number
error.status = res.status
throw error
}
return res.json()
}
export function parseISOTimestamp(isoString: string): number {
return new Date(isoString).getTime()
}
export function formatRelativeTime(timestamp: number): string {
const now = Date.now()
const diff = now - timestamp
const hours = Math.floor(diff / (1000 * 60 * 60))
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
const seconds = Math.floor((diff % (1000 * 60)) / 1000)
if (hours > 24) {
const days = Math.floor(hours / 24)
return `${days}d`
} else if (hours > 0) {
return `${hours}h`
} else if (minutes > 0) {
return `${minutes}m`
} else if (seconds >= 0) {
return `${seconds}s`
}
return "0s"
}
export function formatTime(timestamp: number): string {
const date = new Date(timestamp)
const year = date.getFullYear()
const month = date.getMonth() + 1
const day = date.getDate()
const hours = date.getHours().toString().padStart(2, "0")
const minutes = date.getMinutes().toString().padStart(2, "0")
const seconds = date.getSeconds().toString().padStart(2, "0")
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
interface BillingData {
startDate: string
endDate: string
autoRenewal: string
cycle: string
amount: string
}
interface PlanData {
bandwidth: string
trafficVol: string
trafficType: string
IPv4: string
IPv6: string
networkRoute: string
extra: string
}
export interface PublicNoteData {
billingDataMod?: BillingData
planDataMod?: PlanData
}
export function parsePublicNote(publicNote: string): PublicNoteData | null {
try {
if (!publicNote) {
return null
}
const data = JSON.parse(publicNote)
if (!data.billingDataMod && !data.planDataMod) {
return null
}
if (data.billingDataMod && !data.planDataMod) {
return {
billingDataMod: {
startDate: data.billingDataMod.startDate || "",
endDate: data.billingDataMod.endDate,
autoRenewal: data.billingDataMod.autoRenewal || "",
cycle: data.billingDataMod.cycle || "",
amount: data.billingDataMod.amount || "",
},
}
}
if (!data.billingDataMod && data.planDataMod) {
return {
planDataMod: {
bandwidth: data.planDataMod.bandwidth || "",
trafficVol: data.planDataMod.trafficVol || "",
trafficType: data.planDataMod.trafficType || "",
IPv4: data.planDataMod.IPv4 || "",
IPv6: data.planDataMod.IPv6 || "",
networkRoute: data.planDataMod.networkRoute || "",
extra: data.planDataMod.extra || "",
},
}
}
return {
billingDataMod: {
startDate: data.billingDataMod.startDate || "",
endDate: data.billingDataMod.endDate,
autoRenewal: data.billingDataMod.autoRenewal || "",
cycle: data.billingDataMod.cycle || "",
amount: data.billingDataMod.amount || "",
},
planDataMod: {
bandwidth: data.planDataMod.bandwidth || "",
trafficVol: data.planDataMod.trafficVol || "",
trafficType: data.planDataMod.trafficType || "",
IPv4: data.planDataMod.IPv4 || "",
IPv6: data.planDataMod.IPv6 || "",
networkRoute: data.planDataMod.networkRoute || "",
extra: data.planDataMod.extra || "",
},
}
} catch (error) {
console.error("Error parsing public note:", error)
return null
}
}
// Function to handle public_note with sessionStorage
export function handlePublicNote(serverId: number, publicNote: string): string {
const storageKey = `server_${serverId}_public_note`
const storedNote = sessionStorage.getItem(storageKey)
if (!publicNote && storedNote) {
return storedNote
}
if (publicNote) {
sessionStorage.setItem(storageKey, publicNote)
return publicNote
}
return ""
}