mirror of
https://github.com/Buriburizaem0n/nezha-dash-v1.git
synced 2026-02-05 21:20:08 +00:00
update: init
This commit is contained in:
30
src/lib/nav-router.ts
Normal file
30
src/lib/nav-router.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export const navRouter = [
|
||||
{
|
||||
name: "服务器",
|
||||
path: "/",
|
||||
},
|
||||
{
|
||||
name: "服务(Dev)",
|
||||
path: "/service",
|
||||
},
|
||||
{
|
||||
name: "任务(Dev)",
|
||||
path: "/task",
|
||||
},
|
||||
{
|
||||
name: "告警(Dev)",
|
||||
path: "/alarm",
|
||||
},
|
||||
{
|
||||
name: "内网穿透(Dev)",
|
||||
path: "/intranet",
|
||||
},
|
||||
{
|
||||
name: "用户",
|
||||
path: "/user",
|
||||
},
|
||||
{
|
||||
name: "设置(Dev)",
|
||||
path: "/setting",
|
||||
},
|
||||
];
|
||||
80
src/lib/nezha-api.ts
Normal file
80
src/lib/nezha-api.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
export const fetchUsers = async (token: string) => {
|
||||
const response = await fetch("http://localhost:8008/api/v1/user", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createUser = async (
|
||||
token: string,
|
||||
username: string,
|
||||
password: string,
|
||||
) => {
|
||||
const response = await fetch(`http://localhost:8008/api/v1/user`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ username, password }),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteUser = async (token: string, ids: number[]) => {
|
||||
const response = await fetch(
|
||||
`http://localhost:8008/api/v1/batch-delete/user`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const fetchServers = async (token: string) => {
|
||||
const response = await fetch("http://localhost:8008/api/v1/server", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
export const deleteServer = async (token: string, ids: number[]) => {
|
||||
const response = await fetch(
|
||||
`http://localhost:8008/api/v1/batch-delete/server`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify(ids),
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
},
|
||||
);
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
79
src/lib/nezha-model.ts
Normal file
79
src/lib/nezha-model.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* model.Server
|
||||
*/
|
||||
export interface ModelServer {
|
||||
created_at: string;
|
||||
/**
|
||||
* DDNS配置
|
||||
*/
|
||||
ddns_profiles: number[];
|
||||
deleted_at: string;
|
||||
/**
|
||||
* 展示排序,越大越靠前
|
||||
*/
|
||||
display_index: number;
|
||||
/**
|
||||
* 启用DDNS
|
||||
*/
|
||||
enable_ddns: boolean;
|
||||
/**
|
||||
* 对游客隐藏
|
||||
*/
|
||||
hide_for_guest?: boolean;
|
||||
host?: ModelHost;
|
||||
id: number;
|
||||
last_active?: string;
|
||||
name: string;
|
||||
/**
|
||||
* 管理员可见备注
|
||||
*/
|
||||
note: string;
|
||||
/**
|
||||
* 公开备注
|
||||
*/
|
||||
public_note: string;
|
||||
state: ModelHostState;
|
||||
updated_at: string;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
export interface ModelHost {
|
||||
arch?: string;
|
||||
boot_time?: number;
|
||||
country_code?: string;
|
||||
cpu?: string[];
|
||||
disk_total?: number;
|
||||
gpu?: string[];
|
||||
ip?: string;
|
||||
mem_total?: number;
|
||||
platform?: string;
|
||||
platform_version?: string;
|
||||
swap_total?: number;
|
||||
version?: string;
|
||||
virtualization?: string;
|
||||
}
|
||||
|
||||
export interface ModelHostState {
|
||||
cpu?: number;
|
||||
disk_used?: number;
|
||||
gpu?: number[];
|
||||
load_1?: number;
|
||||
load_15?: number;
|
||||
load_5?: number;
|
||||
mem_used?: number;
|
||||
net_in_speed?: number;
|
||||
net_in_transfer?: number;
|
||||
net_out_speed?: number;
|
||||
net_out_transfer?: number;
|
||||
process_count?: number;
|
||||
swap_used?: number;
|
||||
tcp_conn_count?: number;
|
||||
temperatures?: ModelSensorTemperature[];
|
||||
udp_conn_count?: number;
|
||||
uptime?: number;
|
||||
}
|
||||
|
||||
export interface ModelSensorTemperature {
|
||||
name?: string;
|
||||
temperature?: number;
|
||||
}
|
||||
89
src/lib/useWebsocket.tsx
Normal file
89
src/lib/useWebsocket.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { useState, useEffect, useRef, useCallback } from 'react'
|
||||
|
||||
export interface WebSocketHook {
|
||||
socket: WebSocket | null
|
||||
connected: boolean
|
||||
onlineCount: number
|
||||
message: string | null
|
||||
sendMessage: (msg: string) => void
|
||||
}
|
||||
|
||||
export default function useWebSocket(url: string): WebSocketHook {
|
||||
const [socket, setSocket] = useState<WebSocket | null>(null)
|
||||
const [message, setMessage] = useState<string | null>(null)
|
||||
const [connected, setConnected] = useState<boolean>(false)
|
||||
const [onlineCount, setOnlineCount] = useState<number>(0)
|
||||
const socketRef = useRef<WebSocket | null>(null)
|
||||
const reconnectAttempts = useRef<number>(0)
|
||||
const reconnectTimeout = useRef<NodeJS.Timeout | null>(null)
|
||||
const isUnmounted = useRef<boolean>(false)
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (isUnmounted.current) return
|
||||
|
||||
const ws = new WebSocket(url)
|
||||
setSocket(ws)
|
||||
socketRef.current = ws
|
||||
|
||||
ws.onopen = () => {
|
||||
setConnected(true)
|
||||
reconnectAttempts.current = 0
|
||||
}
|
||||
|
||||
ws.onmessage = (event: MessageEvent) => {
|
||||
setMessage(event.data)
|
||||
const msgJson = JSON.parse(event.data)
|
||||
if (msgJson.type === 'live') {
|
||||
setOnlineCount(msgJson.data.count)
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket Error:', error)
|
||||
}
|
||||
|
||||
ws.onclose = () => {
|
||||
setConnected(false)
|
||||
if (!isUnmounted.current) {
|
||||
// Attempt to reconnect
|
||||
if (reconnectAttempts.current < 5) {
|
||||
const timeout = Math.pow(2, reconnectAttempts.current) * 1000 // Exponential backoff
|
||||
reconnectAttempts.current += 1
|
||||
reconnectTimeout.current = setTimeout(() => {
|
||||
connect()
|
||||
}, timeout)
|
||||
} else {
|
||||
console.warn('Max reconnect attempts reached.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [url])
|
||||
|
||||
useEffect(() => {
|
||||
connect()
|
||||
|
||||
return () => {
|
||||
isUnmounted.current = true
|
||||
if (socketRef.current) {
|
||||
socketRef.current.close()
|
||||
}
|
||||
if (reconnectTimeout.current) {
|
||||
clearTimeout(reconnectTimeout.current)
|
||||
}
|
||||
}
|
||||
}, [connect])
|
||||
|
||||
// Function to send messages
|
||||
const sendMessage = useCallback((msg: string) => {
|
||||
if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
|
||||
socketRef.current.send(msg)
|
||||
} else {
|
||||
console.warn(
|
||||
'WebSocket is not open. Ready state:',
|
||||
socketRef.current?.readyState
|
||||
)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return { socket, message, sendMessage, connected, onlineCount }
|
||||
}
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
26
src/lib/websocketProvider.tsx
Normal file
26
src/lib/websocketProvider.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createContext, useContext, ReactNode } from 'react'
|
||||
import useWebSocket, { WebSocketHook } from './useWebsocket'
|
||||
|
||||
|
||||
interface WebSocketProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const WebSocketContext = createContext<WebSocketHook | undefined>(undefined)
|
||||
|
||||
export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
|
||||
const ws = useWebSocket('wss://dev-next.buycoffee.top:4433/api/v1/ws/server')
|
||||
return (
|
||||
<WebSocketContext.Provider value={ws}>{children}</WebSocketContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useWebSocketContext = (): WebSocketHook => {
|
||||
const context = useContext(WebSocketContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useWebSocketContext must be used within a WebSocketProvider'
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
Reference in New Issue
Block a user