system info collect and show

master
veypi 1 year ago
parent bb49108a5f
commit 583bca4817

@ -2,7 +2,7 @@
uuid: FR9P5t8debxc11aFF
key: AMpjwQHwVjGsb1WC4WG6
debug: true
server_url: 127.0.0.1:4001
server_url: 0.0.0.0:4001
media_path: /Users/veypi/test/media
db_url: localhost:3306
db_user: root
@ -21,9 +21,9 @@ nats_sys:
- UCOKXBGDAXXQOR4XUPUJ4O22HZ2A3KQN3JLCCYM3ISSKHLBZJXXQ3NLF
- SUAEILQZDD2UT2ZNR6DCA44YCRKAZDYDOJRUPAUA7AOWFVGSSPFPCLXF24
info:
ws_url: 127.0.0.1:4221
nats_url: 127.0.0.1:4222
api_url: 127.0.0.1:4001
ws_url: 198.19.249.3:4221
nats_url: 198.19.249.3:4222
api_url: 198.19.249.3:4001
user_init_space: 300

@ -22,6 +22,7 @@ use crate::{AppState, Result};
pub fn routes(cfg: &mut web::ServiceConfig) {
cfg.service(info);
cfg.service(proxynats);
cfg.service(tsdb);
cfg.service(upload::save_files);
cfg.service(user::get)
.service(user::list)
@ -81,3 +82,22 @@ pub async fn proxynats(
let data = reqwest::get(url).await.unwrap().bytes().await.unwrap();
Ok(actix_web::HttpResponse::Ok().body(data))
}
#[actix_web::get("/ts/{p:.*}")]
pub async fn tsdb(
req: actix_web::HttpRequest,
p: web::Path<String>,
) -> Result<impl actix_web::Responder> {
let data = req.uri().query();
let p = p.into_inner();
let mut url = "http://127.0.0.1:8428/api/v1".to_string();
if !p.is_empty() {
url = format!("{url}/{p}")
}
if let Some(query) = data {
url = format!("{url}?{query}")
};
info!(url);
let data = reqwest::get(url).await.unwrap().bytes().await.unwrap();
Ok(actix_web::HttpResponse::Ok().body(data))
}

@ -218,6 +218,7 @@ pub struct UpdateOpt {
pub nickname: Option<String>,
pub email: Option<String>,
pub phone: Option<String>,
pub test: serde_json::Value,
}
#[patch("/user/{id}")]
@ -228,6 +229,7 @@ pub async fn update(
stat: web::Data<AppState>,
data: web::Json<UpdateOpt>,
) -> Result<impl Responder> {
info!("{:#?}", data.test);
Ok("")
}

@ -22,6 +22,7 @@
"animate.css": "^4.1.1",
"axios": "^1.2.1",
"cherry-markdown": "^0.8.26",
"echarts": "^5.4.3",
"js-base64": "^3.7.5",
"mathjax": "3",
"mitt": "^3.0.1",

@ -12,6 +12,7 @@ import user from "./user";
import resource from "./resource";
import access from './access';
import nats from './nats'
import tsdb from './tsdb'
@ -22,6 +23,7 @@ const api = {
role,
resource,
access,
tsdb,
nats
}

@ -0,0 +1,25 @@
/*
* tsdb.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-20 23:21
* Distributed under terms of the MIT license.
*/
import ajax from './axios'
export default {
local: './ts/',
range(query: string, props?: { start?: string, end?: string, step?: string, query?: string }) {
if (query !== undefined) {
// @ts-ignore
props.query = query
} else {
props = { query: query }
}
return ajax.get(this.local + 'query_range', props)
},
query(query: string) {
return ajax.get(this.local + 'query', { query: query })
}
}

@ -35,6 +35,7 @@ export default {
return ajax.get(this.local, props)
},
update(id: string, props: any) {
props.test = { a: 1 }
return ajax.patch(this.local + id, props)
},
}

@ -0,0 +1,173 @@
<!--
* tschart.vue
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-20 22:50
* Distributed under terms of the MIT license.
-->
<template>
<div class="w-full h-full">
<div class="v-chart w-full h-full" ref="chartdom"></div>
</div>
</template>
<script lang="ts" setup>
import * as echart from 'echarts'
import api from 'src/boot/api';
import { onMounted, onUnmounted, computed, ref, watch, markRaw } from 'vue';
interface Item {
name: string
query: string | string[]
ext?: string
valueFormatter?: (s: number) => string
label?: string | string[] | ((s: any) => string)
}
let props = withDefaults(defineProps<{
item: Item,
// start?: string,
// end?: string,
// step?: string
}>(),
{
}
)
let getparams = ref<any>({
start: () => {
let d = new Date()
d.setMinutes(d.getMinutes() - 3)
return d.toISOString()
}, end: undefined, step: '2s'
})
let count = 0
let timer = ref<any[]>([])
let chartdom = ref()
let options = ref<{ [key: string]: any }>({})
let chart: echart.ECharts = {} as any
let tooltip = {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {},
},
valueFormatter: (value: number) => value.toFixed(2),
className: 'v-echarts-tooltip',
}
const init_chart = () => {
count++
if (chart.clear) {
chart.clear()
}
options.value = {
animationThreshold: 200,
tooltip: Object.assign({}, tooltip),
axisPointer: {
link: { xAxisIndex: 'all' },
label: {
backgroundColor: '#777'
}
},
xAxis: {
type: 'time',
},
yAxis: {},
series: []
}
if (props.item.valueFormatter) {
options.value.tooltip.valueFormatter = props.item.valueFormatter
}
let tmp = {} as any
if (getparams.value.start) {
tmp.start = getparams.value.start()
}
if (getparams.value.step) {
tmp.step = getparams.value.step
}
let query: string[] = Array.isArray(props.item.query) ? props.item.query :
[props.item.query]
for (let q = 0; q < query.length; q++) {
api.tsdb.range(query[q], tmp).then(e => {
if (e.status == 'success') {
let data = e.data.result as any[]
if (data.length == 0) {
console.warn('not get data')
return
}
let idx = options.value.series.length || 0
data.forEach(d => {
let name = props.item.name
if (typeof props.item.label === 'string') {
name = props.item.label
} else if (typeof props.item.label === 'function') {
name = props.item.label(d.metric)
} else if (Array.isArray(props.item.label)) {
name = props.item.label[q]
}
options.value.series.push({
name: name,
data: d.values.map((e: any) =>
[e[0] * 1000, Number(e[1])]),
metric: d.metric,
origin: query[q],
symbol: 'none',
smooth: true,
type: 'line',
})
})
chart.setOption(options.value)
let t = setInterval(() => {
sync_chart(idx, query[q], count)
}, 1000)
timer.value.push(t)
}
})
}
// let query = props.query
}
const sync_chart = (idx: number, query: string, c: number) => {
api.tsdb.query(query).then(e => {
if (e.status == 'success') {
let data = e.data.result as any[]
if (data.length == 0) {
console.warn('not get data')
return
}
if (count === c) {
data.forEach((d, i) => {
options.value.series[idx + i].data.push([d.value[0] * 1000,
Number(d.value[1])])
})
chart.setOption(options.value)
}
}
})
}
watch(computed(() => props.item), q => {
timer.value.forEach(e => {
clearInterval(e)
})
if (q) {
init_chart()
}
}, { immediate: true })
onMounted(() => {
chart = markRaw(echart.init(chartdom.value))
})
onUnmounted(() => {
timer.value.forEach(e => {
clearInterval(e)
})
})
</script>
<style>
.v-chart {}
.v-echarts-tooltip {
/* height: 5rem; */
/* width: 10rem; */
}
</style>

@ -26,8 +26,6 @@
.page-h1 {
font-size: 2.5rem;
line-height: 2.5rem;
margin-left: 2.5rem;
margin-top: 1.5rem;
margin-bottom: 2rem;
}

@ -37,7 +37,9 @@
<q-page class="w-full">
<router-view v-slot="{ Component }">
<transition mode="out-in" enter-active-class="animate__fadeIn" leave-active-class="animate__fadeOut">
<component class="animate__animated animate__400ms" :is="Component"></component>
<component class="animate__animated animate__400ms py-8
px-8 h-full
w-full" :is="Component"></component>
</transition>
</router-view>
</q-page>

@ -56,6 +56,11 @@ const defaultLinks: MenuLink[] = [
icon: 'v-user',
to: { name: 'user' }
},
{
title: '系统监控',
icon: 'v-data-view',
to: { name: 'stats' }
},
{
title: '文档中心',
icon: 'v-file-exception',

@ -30,7 +30,8 @@
<div>{{ c.cid }}</div>
<div>{{ c.name || '无' }}</div>
<div>{{ new Date(c.start).toLocaleString() }}</div>
<div>{{ c.subscriptions_list.sort().join(' ') }}</div>
<div>{{ c.subscriptions_list ?
c.subscriptions_list.sort().join(' ') : '' }}</div>
</template>
</div>
</div>

@ -0,0 +1,69 @@
<!--
* stats.vue
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-20 22:56
* Distributed under terms of the MIT license.
-->
<template>
<div class="flex flex-nowrap">
<div class="grow" style="height: calc(100%);">
<tschart :item="querys[idx]"></tschart>
</div>
<div class="flex flex-col gap-5">
<q-chip :color="idx === i ? 'primary' : ''" class="select-none" v-for="(q, i) in querys" :key="i" @click="idx = i"
clickable>{{
q.name }} </q-chip>
</div>
</div>
</template>
<script lang="ts" setup>
import tschart from 'src/components/tschart.vue';
import { ref } from 'vue';
const idx = ref(0)
const querys = ref([
{
name: 'cpu占用',
query: `100 - avg (irate(node_cpu_seconds_total{mode="idle"}[3s]))
by(id) * 100`,
label: (d: any) => d.id as string,
valueFormatter: (value: number) => value.toFixed(2) + "%",
},
{
name: 'linux内存使用率',
query: [
`((node_memory_Buffers_bytes + node_memory_Cached_bytes +
node_memory_MemFree_bytes) / node_memory_MemTotal_bytes) * 100`,
],
label: (d: any) => d.id as string,
valueFormatter: (value: number) => value.toFixed(2) + "%",
},
{
name: 'linux 内存',
query: [
`(node_memory_Buffers_bytes + node_memory_Cached_bytes +
node_memory_MemFree_bytes) / 1024 / 1024 / 1024`,
`node_memory_MemTotal_bytes / 1024 /1024 / 1024`
],
label: ['使用内存', '总内存'],
valueFormatter: (value: number) => value.toFixed(2) + "GB",
},
{
name: 'Mac cpu频率',
query: 'node_cpu_seconds_total',
label: (d: any) => `cpu: ${d.cpu} mode: ${d.mode}` as string
},
{
name: 'mem',
query: 'node_memory_free_bytes / 1024 / 1024 / 1024'
},
{
name: 'Mac swap',
query: 'node_memory_swap_used_bytes / 1024 / 1024 '
},
])
</script>
<style scoped></style>

@ -14,7 +14,6 @@ declare module 'vue-router' {
}
function loadcomponents(path: string, name: string, main: string) {
return {
path: path,
name: name,
@ -43,6 +42,7 @@ const routes: RouteRecordRaw[] = [
loadcomponents('user', 'user', 'user'),
loadcomponents('fs', 'fs', 'fs'),
loadcomponents('doc', 'doc', 'doc'),
loadcomponents('stats', 'stats', 'stats'),
loadcomponents('doc/:typ/:url(.*)', 'doc_item', 'docItem'),
loadcomponents('settings', 'settings', 'settings'),
],

@ -1686,6 +1686,14 @@ dot-prop@6.0.1:
dependencies:
is-obj "^2.0.0"
echarts@^5.4.3:
version "5.4.3"
resolved "https://registry.yarnpkg.com/echarts/-/echarts-5.4.3.tgz#f5522ef24419164903eedcfd2b506c6fc91fb20c"
integrity sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==
dependencies:
tslib "2.3.0"
zrender "5.4.4"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@ -4126,6 +4134,11 @@ ts-nkeys@^1.0.16:
dependencies:
tweetnacl "^1.0.3"
tslib@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@ -4526,3 +4539,10 @@ zip-stream@^4.1.0:
archiver-utils "^3.0.4"
compress-commons "^4.1.2"
readable-stream "^3.6.0"
zrender@5.4.4:
version "5.4.4"
resolved "https://registry.yarnpkg.com/zrender/-/zrender-5.4.4.tgz#8854f1d95ecc82cf8912f5a11f86657cb8c9e261"
integrity sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==
dependencies:
tslib "2.3.0"

Loading…
Cancel
Save