refactor: port system information checker to backend-rs

network stat is removed because it might be inaccurate and/or
it should be monitored by other system tools, but it may be added back
later if it is wanted
This commit is contained in:
naskya 2024-05-15 16:26:46 +09:00
parent c2d5859755
commit d8e1ab63c0
No known key found for this signature in database
GPG Key ID: 712D413B3A9FED5C
29 changed files with 279 additions and 396 deletions

35
Cargo.lock generated
View File

@ -236,6 +236,7 @@ dependencies = [
"serde_json",
"serde_yaml",
"strum 0.26.2",
"sysinfo",
"thiserror",
"tokio",
"tracing",
@ -1644,6 +1645,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
"winapi",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -3167,6 +3177,21 @@ dependencies = [
"syn 2.0.62",
]
[[package]]
name = "sysinfo"
version = "0.30.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae"
dependencies = [
"cfg-if",
"core-foundation-sys",
"libc",
"ntapi",
"once_cell",
"rayon",
"windows",
]
[[package]]
name = "system-deps"
version = "6.2.2"
@ -3673,6 +3698,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be"
dependencies = [
"windows-core",
"windows-targets 0.52.5",
]
[[package]]
name = "windows-core"
version = "0.52.0"

View File

@ -35,6 +35,7 @@ serde_json = "1.0.117"
serde_yaml = "0.9.34"
strum = "0.26.2"
syn = "2.0.62"
sysinfo = "0.30.12"
thiserror = "1.0.60"
tokio = "1.37.0"
tracing = "0.1.40"

View File

@ -4,6 +4,8 @@ Breaking changes are indicated by the :warning: icon.
## Unreleased
- :warning: `server-info` (an endpoint to get server hardware information) now requires credentials.
- :warning: `net` (server's default network interface) has been removed from `admin/server-info`.
- Adding `lang` to the response of `i` and the request parameter of `i/update`.
## v20240504

View File

@ -39,6 +39,7 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
strum = { workspace = true, features = ["derive"] }
sysinfo = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tracing = { workspace = true }

View File

@ -212,6 +212,8 @@ export interface Acct {
}
export function stringToAcct(acct: string): Acct
export function acctToString(acct: Acct): string
export function initializeRustLogger(): void
export function showServerInfo(): void
export function addNoteToAntenna(antennaId: string, note: Note): void
/**
* Checks if a server is blocked.
@ -299,6 +301,28 @@ export function countReactions(reactions: Record<string, number>): Record<string
export function toDbReaction(reaction?: string | undefined | null, host?: string | undefined | null): Promise<string>
/** Delete all entries in the "attestation_challenge" table created at more than 5 minutes ago */
export function removeOldAttestationChallenges(): Promise<void>
export interface Cpu {
model: string
cores: number
}
export interface Memory {
/** Total memory amount in bytes */
total: number
/** Used memory amount in bytes */
used: number
/** Available (for (re)use) memory amount in bytes */
available: number
}
export interface Storage {
/** Total storage space in bytes */
total: number
/** Used storage space in bytes */
used: number
}
export function cpuInfo(): Cpu
export function cpuUsage(): number
export function memoryUsage(): Memory
export function storageUsage(): Storage | null
export interface AbuseUserReport {
id: string
createdAt: Date
@ -1156,7 +1180,6 @@ export interface Webhook {
latestSentAt: Date | null
latestStatus: number | null
}
export function initializeRustLogger(): void
export function fetchNodeinfo(host: string): Promise<Nodeinfo>
export function nodeinfo_2_1(): Promise<any>
export function nodeinfo_2_0(): Promise<any>

View File

@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initializeRustLogger, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, watchNote, unwatchNote, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, loadConfig, stringToAcct, acctToString, initializeRustLogger, showServerInfo, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isSafeUrl, latestVersion, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, watchNote, unwatchNote, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
module.exports.SECOND = SECOND
module.exports.MINUTE = MINUTE
@ -323,6 +323,8 @@ module.exports.loadEnv = loadEnv
module.exports.loadConfig = loadConfig
module.exports.stringToAcct = stringToAcct
module.exports.acctToString = acctToString
module.exports.initializeRustLogger = initializeRustLogger
module.exports.showServerInfo = showServerInfo
module.exports.addNoteToAntenna = addNoteToAntenna
module.exports.isBlockedServer = isBlockedServer
module.exports.isSilencedServer = isSilencedServer
@ -353,6 +355,10 @@ module.exports.decodeReaction = decodeReaction
module.exports.countReactions = countReactions
module.exports.toDbReaction = toDbReaction
module.exports.removeOldAttestationChallenges = removeOldAttestationChallenges
module.exports.cpuInfo = cpuInfo
module.exports.cpuUsage = cpuUsage
module.exports.memoryUsage = memoryUsage
module.exports.storageUsage = storageUsage
module.exports.AntennaSrcEnum = AntennaSrcEnum
module.exports.DriveFileUsageHintEnum = DriveFileUsageHintEnum
module.exports.MutedNoteReasonEnum = MutedNoteReasonEnum
@ -364,7 +370,6 @@ module.exports.RelayStatusEnum = RelayStatusEnum
module.exports.UserEmojimodpermEnum = UserEmojimodpermEnum
module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum
module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum
module.exports.initializeRustLogger = initializeRustLogger
module.exports.fetchNodeinfo = fetchNodeinfo
module.exports.nodeinfo_2_1 = nodeinfo_2_1
module.exports.nodeinfo_2_0 = nodeinfo_2_0

View File

@ -0,0 +1,2 @@
pub mod log;
pub mod server_stats;

View File

@ -0,0 +1,39 @@
use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError};
use sysinfo::System;
pub type SystemMutexError = PoisonError<MutexGuard<'static, System>>;
// TODO: handle this in more proper way when we move the entry point to backend-rs
pub fn system() -> Result<MutexGuard<'static, System>, SystemMutexError> {
pub static SYSTEM: OnceLock<Mutex<System>> = OnceLock::new();
SYSTEM.get_or_init(|| Mutex::new(System::new_all())).lock()
}
#[crate::export]
pub fn show_server_info() -> Result<(), SystemMutexError> {
let system_info = system()?;
tracing::info!(
"Hostname: {}",
System::host_name().unwrap_or("unknown".to_string())
);
tracing::info!(
"OS: {}",
System::long_os_version().unwrap_or("unknown".to_string())
);
tracing::info!(
"Kernel: {}",
System::kernel_version().unwrap_or("unknown".to_string())
);
tracing::info!(
"CPU architecture: {}",
System::cpu_arch().unwrap_or("unknown".to_string())
);
tracing::info!("CPU threads: {}", system_info.cpus().len());
tracing::info!("Total memory: {} MiB", system_info.total_memory() / 1048576);
tracing::info!("Free memory: {} MiB", system_info.free_memory() / 1048576);
tracing::info!("Total swap: {} MiB", system_info.total_swap() / 1048576);
tracing::info!("Free swap: {} MiB", system_info.free_swap() / 1048576);
Ok(())
}

View File

@ -3,6 +3,7 @@ pub use macro_rs::{export, ts_only_warn};
pub mod config;
pub mod database;
pub mod federation;
pub mod init;
pub mod misc;
pub mod model;
pub mod service;

View File

@ -15,3 +15,4 @@ pub mod nyaify;
pub mod password;
pub mod reaction;
pub mod remove_old_attestation_challenges;
pub mod server_stats;

View File

@ -0,0 +1,90 @@
use crate::init::server_stats::{system, SystemMutexError};
use sysinfo::{Disks, MemoryRefreshKind};
// TODO: i64 -> u64 (we can't export u64 to Node.js)
#[crate::export(object)]
pub struct Cpu {
pub model: String,
// TODO: u16 -> usize (we can't export usize to Node.js)
pub cores: u16,
}
#[crate::export(object)]
pub struct Memory {
/// Total memory amount in bytes
pub total: i64,
/// Used memory amount in bytes
pub used: i64,
/// Available (for (re)use) memory amount in bytes
pub available: i64,
}
#[crate::export(object)]
pub struct Storage {
/// Total storage space in bytes
pub total: i64,
/// Used storage space in bytes
pub used: i64,
}
#[crate::export]
pub fn cpu_info() -> Result<Cpu, SystemMutexError> {
let system_info = system()?;
Ok(Cpu {
model: match system_info.cpus() {
cpus if cpus.is_empty() => {
tracing::debug!("failed to get CPU info");
"unknown".to_string()
}
cpus => cpus[0].brand().to_string(),
},
cores: system_info.cpus().len() as u16,
})
}
#[crate::export]
pub fn cpu_usage() -> Result<f32, SystemMutexError> {
let mut system_info = system()?;
system_info.refresh_cpu_usage();
let total_cpu_usage: f32 = system_info.cpus().iter().map(|cpu| cpu.cpu_usage()).sum();
let cpu_threads = system_info.cpus().len();
Ok(total_cpu_usage / (cpu_threads as f32))
}
#[crate::export]
pub fn memory_usage() -> Result<Memory, SystemMutexError> {
let mut system_info = system()?;
system_info.refresh_memory_specifics(MemoryRefreshKind::new().with_ram());
Ok(Memory {
total: system_info.total_memory() as i64,
used: system_info.used_memory() as i64,
available: system_info.available_memory() as i64,
})
}
#[crate::export]
pub fn storage_usage() -> Option<Storage> {
// Get the first disk that is actualy used.
let disks = Disks::new_with_refreshed_list();
let disk = disks
.iter()
.find(|disk| disk.available_space() > 0 && disk.total_space() > disk.available_space());
if let Some(disk) = disk {
let total = disk.total_space() as i64;
let available = disk.available_space() as i64;
return Some(Storage {
total,
used: total - available,
});
}
tracing::debug!("failed to get stats");
None
}

View File

@ -1,4 +1,3 @@
pub mod log;
pub mod nodeinfo;
pub mod note;
pub mod stream;

View File

@ -87,7 +87,6 @@
"node-fetch": "3.3.2",
"nodemailer": "6.9.13",
"opencc-js": "1.0.5",
"os-utils": "0.0.14",
"otpauth": "9.2.4",
"parse5": "7.1.2",
"pg": "8.11.5",
@ -111,7 +110,6 @@
"stringz": "2.1.0",
"summaly": "2.7.0",
"syslog-pro": "1.0.0",
"systeminformation": "5.22.8",
"tar-stream": "3.1.7",
"tesseract.js": "5.1.0",
"tinycolor2": "1.6.0",

View File

@ -1,33 +0,0 @@
declare module "os-utils" {
type FreeCommandCallback = (usedmem: number) => void;
type HarddriveCallback = (total: number, free: number, used: number) => void;
type GetProcessesCallback = (result: string) => void;
type CPUCallback = (perc: number) => void;
export function platform(): NodeJS.Platform;
export function cpuCount(): number;
export function sysUptime(): number;
export function processUptime(): number;
export function freemem(): number;
export function totalmem(): number;
export function freememPercentage(): number;
export function freeCommand(callback: FreeCommandCallback): void;
export function harddrive(callback: HarddriveCallback): void;
export function getProcesses(callback: GetProcessesCallback): void;
export function getProcesses(
nProcess: number,
callback: GetProcessesCallback,
): void;
export function allLoadavg(): string;
export function loadavg(_time?: number): number;
export function cpuFree(callback: CPUCallback): void;
export function cpuUsage(callback: CPUCallback): void;
}

View File

@ -12,10 +12,10 @@ import {
fetchMeta,
initializeRustLogger,
removeOldAttestationChallenges,
showServerInfo,
type Config,
} from "backend-rs";
import { config, envOption } from "@/config.js";
import { showMachineInfo } from "@/misc/show-machine-info.js";
import { db, initDb } from "@/db/postgre.js";
import { inspect } from "node:util";
@ -93,12 +93,12 @@ function greet() {
export async function masterMain() {
// initialize app
try {
initializeRustLogger();
greet();
showEnvironment();
await showMachineInfo(bootLogger);
showServerInfo();
showNodejsVersion();
await connectDb();
initializeRustLogger();
} catch (e) {
bootLogger.error(
`Fatal error occurred during initialization:\n${inspect(e)}`,

View File

@ -1,15 +1,8 @@
import si from "systeminformation";
import Xev from "xev";
import * as osUtils from "os-utils";
import { fetchMeta } from "backend-rs";
import { fetchMeta, cpuUsage, memoryUsage } from "backend-rs";
const ev = new Xev();
const interval = 2000;
const roundCpu = (num: number) => Math.round(num * 1000) / 1000;
const round = (num: number) => Math.round(num * 10) / 10;
/**
* Report server stats regularly
*/
@ -24,26 +17,9 @@ export default async function () {
if (!meta.enableServerMachineStats) return;
async function tick() {
const cpu = await cpuUsage();
const memStats = await mem();
const netStats = await net();
const fsStats = await fs();
const stats = {
cpu: roundCpu(cpu),
mem: {
used: round(memStats.used - memStats.buffers - memStats.cached),
active: round(memStats.active),
total: round(memStats.total),
},
net: {
rx: round(Math.max(0, netStats.rx_sec)),
tx: round(Math.max(0, netStats.tx_sec)),
},
fs: {
r: round(Math.max(0, fsStats.rIO_sec ?? 0)),
w: round(Math.max(0, fsStats.wIO_sec ?? 0)),
},
cpu: cpuUsage(),
mem: memoryUsage(),
};
ev.emit("serverStats", stats);
log.unshift(stats);
@ -52,33 +28,5 @@ export default async function () {
tick();
setInterval(tick, interval);
}
// CPU STAT
function cpuUsage(): Promise<number> {
return new Promise((res, rej) => {
osUtils.cpuUsage((cpuUsage) => {
res(cpuUsage);
});
});
}
// MEMORY STAT
async function mem() {
const data = await si.mem();
return data;
}
// NETWORK STAT
async function net() {
const iface = await si.networkInterfaceDefault();
const data = await si.networkStats(iface);
return data[0];
}
// FS STAT
async function fs() {
const data = await si.disksIO().catch(() => ({ rIO_sec: 0, wIO_sec: 0 }));
return data || { rIO_sec: 0, wIO_sec: 0 };
setInterval(tick, 3000);
}

View File

@ -1,17 +0,0 @@
import * as os from "node:os";
import sysUtils from "systeminformation";
import type Logger from "@/services/logger.js";
export async function showMachineInfo(parentLogger: Logger) {
const logger = parentLogger.createSubLogger("machine");
logger.debug(`Hostname: ${os.hostname()}`);
logger.debug(`Platform: ${process.platform} Arch: ${process.arch}`);
const mem = await sysUtils.mem();
const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1);
const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1);
logger.debug(
`CPU: ${
os.cpus().length
} core MEM: ${totalmem}GB (available: ${availmem}GB)`,
);
}

View File

@ -1,8 +1,12 @@
import * as os from "node:os";
import si from "systeminformation";
import define from "@/server/api/define.js";
import { redisClient } from "@/db/redis.js";
import { db } from "@/db/postgre.js";
import {
cpuInfo,
memoryUsage,
storageUsage,
} from "backend-rs";
export const meta = {
requireCredential: true,
@ -85,19 +89,6 @@ export const meta = {
},
},
},
net: {
type: "object",
optional: false,
nullable: false,
properties: {
interface: {
type: "string",
optional: false,
nullable: false,
example: "eth0",
},
},
},
},
},
} as const;
@ -109,13 +100,10 @@ export const paramDef = {
} as const;
export default define(meta, paramDef, async () => {
const memStats = await si.mem();
const fsStats = await si.fsSize();
const netInterface = await si.networkInterfaceDefault();
const redisServerInfo = await redisClient.info("Server");
const m = redisServerInfo.match(new RegExp("^redis_version:(.*)", "m"));
const m = redisServerInfo.match(/^redis_version:(.*)/m);
const redis_version = m?.[1];
const storage = storageUsage();
return {
machine: os.hostname(),
@ -125,19 +113,13 @@ export default define(meta, paramDef, async () => {
.query("SHOW server_version")
.then((x) => x[0].server_version),
redis: redis_version,
cpu: {
model: os.cpus()[0].model,
cores: os.cpus().length,
},
cpu: cpuInfo(),
mem: {
total: memStats.total,
total: memoryUsage().total,
},
fs: {
total: fsStats[0].size,
used: fsStats[0].used,
},
net: {
interface: netInterface,
total: storage?.total ?? 0,
used: storage?.used ?? 0,
},
};
});

View File

@ -1,10 +1,9 @@
import * as os from "node:os";
import si from "systeminformation";
import define from "@/server/api/define.js";
import { fetchMeta } from "backend-rs";
import { fetchMeta, cpuInfo, memoryUsage, storageUsage } from "backend-rs";
export const meta = {
requireCredential: false,
requireCredential: true,
requireCredentialPrivateMode: true,
allowGet: true,
cacheSec: 30,
@ -18,19 +17,8 @@ export const paramDef = {
} as const;
export default define(meta, paramDef, async () => {
const memStats = await si.mem();
const fsStats = await si.fsSize();
let fsIndex = 0;
// Get the first index of fs sizes that are actualy used.
for (const [i, stat] of fsStats.entries()) {
if (stat.rw === true && stat.used > 0) {
fsIndex = i;
break;
}
}
const instanceMeta = await fetchMeta(true);
if (!instanceMeta.enableServerMachineStats) {
return {
machine: "Not specified",
@ -47,18 +35,19 @@ export default define(meta, paramDef, async () => {
},
};
}
const memory = memoryUsage();
const storage = storageUsage();
return {
machine: os.hostname(),
cpu: {
model: os.cpus()[0].model,
cores: os.cpus().length,
},
cpu: cpuInfo(),
mem: {
total: memStats.total,
total: memory.total,
},
fs: {
total: fsStats[fsIndex].size,
used: fsStats[fsIndex].used,
total: storage?.total ?? 0,
used: storage?.used ?? 0,
},
};
});

View File

@ -44,7 +44,6 @@ import icon from "@/scripts/icon";
const stream = useStream();
const meta = await os.api("server-info", {});
const serverStats = await os.api("stats");
const cpuUsage = ref(0);

View File

@ -36,7 +36,7 @@
/>
<text x="1" y="5">
CPU
<tspan>{{ cpuP }}%</tspan>
<tspan>{{ cpuUsage }}%</tspan>
</text>
</svg>
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
@ -75,7 +75,7 @@
/>
<text x="1" y="5">
MEM
<tspan>{{ memP }}%</tspan>
<tspan>{{ memUsage }}%</tspan>
</text>
</svg>
</div>
@ -87,26 +87,25 @@ import { v4 as uuid } from "uuid";
const props = defineProps<{
connection: any;
meta: any;
}>();
const viewBoxX: number = ref(50);
const viewBoxY: number = ref(30);
const stats: any[] = ref([]);
const viewBoxX = ref(50);
const viewBoxY = ref(30);
const stats = ref<any[]>([]);
const cpuGradientId = uuid();
const cpuMaskId = uuid();
const memGradientId = uuid();
const memMaskId = uuid();
const cpuPolylinePoints: string = ref("");
const memPolylinePoints: string = ref("");
const cpuPolygonPoints: string = ref("");
const memPolygonPoints: string = ref("");
const cpuHeadX: any = ref(null);
const cpuHeadY: any = ref(null);
const memHeadX: any = ref(null);
const memHeadY: any = ref(null);
const cpuP: string = ref("");
const memP: string = ref("");
const cpuPolylinePoints = ref("");
const memPolylinePoints = ref("");
const cpuPolygonPoints = ref("");
const memPolygonPoints = ref("");
const cpuHeadX = ref<number>();
const cpuHeadY = ref<number>();
const memHeadX = ref<number>();
const memHeadY = ref<number>();
const cpuUsage = ref<string>();
const memUsage = ref<string>();
onMounted(() => {
props.connection.on("stats", onStats);
@ -127,11 +126,11 @@ function onStats(connStats) {
const cpuPolylinePointsStats = stats.value.map((s, i) => [
viewBoxX.value - (stats.value.length - 1 - i),
(1 - s.cpu) * viewBoxY.value,
(1 - s.cpu / 100) * viewBoxY.value,
]);
const memPolylinePointsStats = stats.value.map((s, i) => [
viewBoxX.value - (stats.value.length - 1 - i),
(1 - s.mem.active / s.mem.total) * viewBoxY.value,
(1 - s.mem.used / s.mem.total) * viewBoxY.value,
]);
cpuPolylinePoints.value = cpuPolylinePointsStats
.map((xy) => `${xy[0]},${xy[1]}`)
@ -152,8 +151,10 @@ function onStats(connStats) {
memHeadX.value = memPolylinePointsStats[memPolylinePointsStats.length - 1][0];
memHeadY.value = memPolylinePointsStats[memPolylinePointsStats.length - 1][1];
cpuP.value = (connStats.cpu * 100).toFixed(0);
memP.value = ((connStats.mem.active / connStats.mem.total) * 100).toFixed(0);
cpuUsage.value = connStats.cpu.toFixed(1);
memUsage.value = ((connStats.mem.used / connStats.mem.total) * 100).toFixed(
1,
);
}
function onStatsLog(statsLog) {

View File

@ -19,10 +19,10 @@ const props = defineProps<{
meta: any;
}>();
const usage: number = ref(0);
const usage = ref(0);
function onStats(stats) {
usage.value = stats.cpu;
usage.value = stats.cpu / 100;
}
onMounted(() => {

View File

@ -4,7 +4,7 @@
<div>
<p><i :class="icon('ph-hard-drives')"></i>Disk</p>
<p>Total: {{ bytes(total, 1) }}</p>
<p>Free: {{ bytes(available, 1) }}</p>
<p>Available: {{ bytes(available, 1) }}</p>
<p>Used: {{ bytes(used, 1) }}</p>
</div>
</div>
@ -18,7 +18,12 @@ import bytes from "@/filters/bytes";
import icon from "@/scripts/icon";
const props = defineProps<{
meta: any; // TODO
meta: {
fs: {
used: number;
total: number;
};
};
}>();
const usage = computed(() => props.meta.fs.used / props.meta.fs.total);

View File

@ -26,23 +26,18 @@
:connection="connection"
:meta="meta"
/>
<XNet
<XCpu
v-else-if="widgetProps.view === 1"
:connection="connection"
:meta="meta"
/>
<XCpu
<XMemory
v-else-if="widgetProps.view === 2"
:connection="connection"
:meta="meta"
/>
<XMemory
v-else-if="widgetProps.view === 3"
:connection="connection"
:meta="meta"
/>
<XDisk
v-else-if="widgetProps.view === 4"
v-else-if="widgetProps.view === 3"
:connection="connection"
:meta="meta"
/>
@ -52,10 +47,13 @@
<script lang="ts" setup>
import { onUnmounted, ref } from "vue";
import type { Widget, WidgetComponentExpose } from "../widget";
import type {
WidgetComponentEmits,
WidgetComponentExpose,
WidgetComponentProps,
} from "../widget";
import { useWidgetPropsManager } from "../widget";
import XCpuMemory from "./cpu-mem.vue";
import XNet from "./net.vue";
import XCpu from "./cpu.vue";
import XMemory from "./mem.vue";
import XDisk from "./disk.vue";
@ -87,11 +85,8 @@ const widgetPropsDef = {
type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
// vueimporttype
// const props = defineProps<WidgetComponentProps<WidgetProps>>();
// const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
const props = defineProps<{ widget?: Widget<WidgetProps> }>();
const emit = defineEmits<{ (ev: "updateProps", props: WidgetProps) }>();
const props = defineProps<WidgetComponentProps<WidgetProps>>();
const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
const { widgetProps, configure, save } = useWidgetPropsManager(
name,
@ -107,14 +102,7 @@ os.apiGet("server-info", {}).then((res) => {
});
const toggleView = () => {
if (
(widgetProps.view === 5 && instance.features.searchFilters) ||
(widgetProps.view === 4 && !instance.features.searchFilters)
) {
widgetProps.view = 0;
} else {
widgetProps.view++;
}
widgetProps.view = (widgetProps.view + 1) % 4;
save();
};

View File

@ -5,7 +5,7 @@
<p><i :class="icon('ph-microchip')"></i>RAM</p>
<p>Total: {{ bytes(total, 1) }}</p>
<p>Used: {{ bytes(used, 1) }}</p>
<p>Free: {{ bytes(free, 1) }}</p>
<p>Available: {{ bytes(available, 1) }}</p>
</div>
</div>
</template>
@ -18,19 +18,18 @@ import icon from "@/scripts/icon";
const props = defineProps<{
connection: any;
meta: any;
}>();
const usage = ref<number>(0);
const total = ref<number>(0);
const used = ref<number>(0);
const free = ref<number>(0);
const available = ref<number>(0);
function onStats(stats) {
usage.value = stats.mem.active / stats.mem.total;
usage.value = stats.mem.used / stats.mem.total;
total.value = stats.mem.total;
used.value = stats.mem.active;
free.value = total.value - used.value;
used.value = stats.mem.used;
available.value = stats.mem.available;
}
onMounted(() => {

View File

@ -1,156 +0,0 @@
<template>
<div class="oxxrhrto">
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
<polygon
:points="inPolygonPoints"
fill="#f6c177"
fill-opacity="0.5"
/>
<polyline
:points="inPolylinePoints"
fill="none"
stroke="#f6c177"
stroke-width="1"
/>
<circle :cx="inHeadX" :cy="inHeadY" r="1.5" fill="#f6c177" />
<text x="1" y="5">
NET rx
<tspan>{{ bytes(inRecent) }}</tspan>
</text>
</svg>
<svg :viewBox="`0 0 ${viewBoxX} ${viewBoxY}`">
<polygon
:points="outPolygonPoints"
fill="#31748f"
fill-opacity="0.5"
/>
<polyline
:points="outPolylinePoints"
fill="none"
stroke="#31748f"
stroke-width="1"
/>
<circle :cx="outHeadX" :cy="outHeadY" r="1.5" fill="#31748f" />
<text x="1" y="5">
NET tx
<tspan>{{ bytes(outRecent) }}</tspan>
</text>
</svg>
</div>
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref } from "vue";
import bytes from "@/filters/bytes";
const props = defineProps<{
connection: any;
meta: any;
}>();
const viewBoxX: number = ref(50);
const viewBoxY: number = ref(30);
const stats: any[] = ref([]);
const inPolylinePoints: string = ref("");
const outPolylinePoints: string = ref("");
const inPolygonPoints: string = ref("");
const outPolygonPoints: string = ref("");
const inHeadX: any = ref(null);
const inHeadY: any = ref(null);
const outHeadX: any = ref(null);
const outHeadY: any = ref(null);
const inRecent: number = ref(0);
const outRecent: number = ref(0);
onMounted(() => {
props.connection.on("stats", onStats);
props.connection.on("statsLog", onStatsLog);
props.connection.send("requestLog", {
id: Math.random().toString().substring(2, 10),
});
});
onBeforeUnmount(() => {
props.connection.off("stats", onStats);
props.connection.off("statsLog", onStatsLog);
});
function onStats(connStats) {
stats.value.push(connStats);
if (stats.value.length > 50) stats.value.shift();
const inPeak = Math.max(
1024 * 64,
Math.max(...stats.value.map((s) => s.net.rx)),
);
const outPeak = Math.max(
1024 * 64,
Math.max(...stats.value.map((s) => s.net.tx)),
);
const inPolylinePointsStats = stats.value.map((s, i) => [
viewBoxX.value - (stats.value.length - 1 - i),
(1 - s.net.rx / inPeak) * viewBoxY.value,
]);
const outPolylinePointsStats = stats.value.map((s, i) => [
viewBoxX.value - (stats.value.length - 1 - i),
(1 - s.net.tx / outPeak) * viewBoxY.value,
]);
inPolylinePoints.value = inPolylinePointsStats
.map((xy) => `${xy[0]},${xy[1]}`)
.join(" ");
outPolylinePoints.value = outPolylinePointsStats
.map((xy) => `${xy[0]},${xy[1]}`)
.join(" ");
inPolygonPoints.value = `${viewBoxX.value - (stats.value.length - 1)},${
viewBoxY.value
} ${inPolylinePoints.value} ${viewBoxX.value},${viewBoxY.value}`;
outPolygonPoints.value = `${viewBoxX.value - (stats.value.length - 1)},${
viewBoxY.value
} ${outPolylinePoints.value} ${viewBoxX.value},${viewBoxY.value}`;
inHeadX.value = inPolylinePointsStats[inPolylinePointsStats.length - 1][0];
inHeadY.value = inPolylinePointsStats[inPolylinePointsStats.length - 1][1];
outHeadX.value = outPolylinePointsStats[outPolylinePointsStats.length - 1][0];
outHeadY.value = outPolylinePointsStats[outPolylinePointsStats.length - 1][1];
inRecent.value = connStats.net.rx;
outRecent.value = connStats.net.tx;
}
function onStatsLog(statsLog) {
for (const revStats of [...statsLog].reverse()) {
onStats(revStats);
}
}
</script>
<style lang="scss" scoped>
.oxxrhrto {
display: flex;
> svg {
display: block;
padding: 10px;
width: 50%;
&:first-child {
padding-right: 5px;
}
&:last-child {
padding-left: 5px;
}
> text {
font-size: 5px;
fill: currentColor;
> tspan {
opacity: 0.5;
}
}
}
}
</style>

View File

@ -19,7 +19,7 @@
:stroke="color"
/>
<text x="50%" y="50%" dy="0.05" text-anchor="middle">
{{ (value * 100).toFixed(0) }}%
{{ (value * 100).toFixed(1) }}%
</text>
</svg>
</template>

View File

@ -237,9 +237,6 @@ importers:
opencc-js:
specifier: 1.0.5
version: 1.0.5
os-utils:
specifier: 0.0.14
version: 0.0.14
otpauth:
specifier: 9.2.4
version: 9.2.4
@ -309,9 +306,6 @@ importers:
syslog-pro:
specifier: 1.0.0
version: 1.0.0
systeminformation:
specifier: 5.22.8
version: 5.22.8
tar-stream:
specifier: 3.1.7
version: 3.1.7
@ -6210,9 +6204,6 @@ packages:
resolution: {integrity: sha512-uksVLsqG3pVdzzPvmAHpBK0wKxYItuzZr7SziusRPoz67tGV8rL1szZ6IdeUrbqLjGDwApBtN29eEE3IqGHOjg==}
engines: {node: '>=4'}
os-utils@0.0.14:
resolution: {integrity: sha512-ajB8csaHLBvJOYsHJkp8YdO2FvlBbf/ZxaYQwXXRDyQ84UoE+uTuLXxqd0shekXMX6Qr/pt/DDyLMRAMsgfWzg==}
otpauth@9.2.4:
resolution: {integrity: sha512-t0Nioq2Up2ZaT5AbpXZLTjrsNtLc/g/rVSaEThmKLErAuT9mrnAKJryiPOKc3rCH+3ycWBgKpRHYn+DHqfaPiQ==}
@ -7285,12 +7276,6 @@ packages:
resolution: {integrity: sha512-7SNMJKtQBJlwBUp1jxFT7bXya71cnINXPCYJ2AVhlQE4MKL7o2QiPdAXbMdWRiLeykQ2rx+7TNrnoGzvzhO+eA==}
engines: {node: '>=10.0.0'}
systeminformation@5.22.8:
resolution: {integrity: sha512-F1iWQ+PSfOzvLMGh2UXASaWLDq5o+1h1db13Kddl6ojcQ47rsJhpMtRrmBXfTA5QJgutC4KV67YRmXLuroIxrA==}
engines: {node: '>=8.0.0'}
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
hasBin: true
syuilo-password-strength@0.0.1:
resolution: {integrity: sha512-g9rPT3V1Q4WjWFZ/t5BdGC1mT/FpYnsLdBl+M5e6MlRkuE1RSR+R43wcY/3mKI59B9KEr+vxdWCuWNMD3oNHKA==}
@ -14397,8 +14382,6 @@ snapshots:
dependencies:
arch: 2.2.0
os-utils@0.0.14: {}
otpauth@9.2.4:
dependencies:
jssha: 3.3.1
@ -15537,8 +15520,6 @@ snapshots:
dependencies:
moment: 2.30.1
systeminformation@5.22.8: {}
syuilo-password-strength@0.0.1: {}
tabbable@6.2.0: {}