master
veypi 12 months ago
parent 583bca4817
commit 73e56ca8c5

@ -4,3 +4,11 @@
[Demo](https://oa.veypi.com) [Demo](https://oa.veypi.com)
### 依赖库
```bash
docker run -dit --name=tsdb -v /Users/veypi/test/vdb:/victoria-metrics-data -p 8428:8428 victoriametrics/victoria-metrics -search.latencyOffset=1s
nats-server -c ./script/nats.cfg
```

@ -21,9 +21,10 @@ nats_sys:
- UCOKXBGDAXXQOR4XUPUJ4O22HZ2A3KQN3JLCCYM3ISSKHLBZJXXQ3NLF - UCOKXBGDAXXQOR4XUPUJ4O22HZ2A3KQN3JLCCYM3ISSKHLBZJXXQ3NLF
- SUAEILQZDD2UT2ZNR6DCA44YCRKAZDYDOJRUPAUA7AOWFVGSSPFPCLXF24 - SUAEILQZDD2UT2ZNR6DCA44YCRKAZDYDOJRUPAUA7AOWFVGSSPFPCLXF24
info: info:
ws_url: 198.19.249.3:4221 ws_url: 127.0.0.1:4221
nats_url: 198.19.249.3:4222 nats_url: 127.0.0.1:4222
api_url: 198.19.249.3:4001 api_url: 127.0.0.1:4001
ts_url: 127.0.0.1:8428
user_init_space: 300 user_init_space: 300

@ -1,3 +1,3 @@
<!DOCTYPE html><html><head><title>OA</title><meta charset=utf-8><meta name=description content=oneauth><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/ico href="/favicon.ico"> <script type="module" crossorigin src="/assets/index.80ecf914.js"></script> <!DOCTYPE html><html><head><title>OA</title><meta charset=utf-8><meta name=description content=oneauth><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/ico href="/favicon.ico"> <script type="module" crossorigin src="/assets/index.559d1c65.js"></script>
<link rel="stylesheet" href="/assets/index.45343e6a.css"> <link rel="stylesheet" href="/assets/index.4abc03f5.css">
</head><body><div id=v-msg></div><div id=q-app></div></body></html> </head><body><div id=v-msg></div><div id=q-app></div></body></html>

@ -114,6 +114,7 @@ pub struct InfoOpt {
pub nats_url: String, pub nats_url: String,
pub ws_url: String, pub ws_url: String,
pub api_url: String, pub api_url: String,
pub ts_url: String,
pub token: Option<String>, pub token: Option<String>,
} }
@ -193,9 +194,10 @@ impl AppState {
], ],
user_init_space: 300, user_init_space: 300,
info: InfoOpt { info: InfoOpt {
ws_url: "http://127.0.0.1:4221".to_string(), ws_url: "127.0.0.1:4221".to_string(),
nats_url: "http://127.0.0.1:4222".to_string(), nats_url: "127.0.0.1:4222".to_string(),
api_url: "http://127.0.0.1:4001".to_string(), api_url: "127.0.0.1:4001".to_string(),
ts_url: "127.0.0.1:8428".to_string(),
token: None, token: None,
}, },
} }

@ -13,20 +13,20 @@ use std::sync::{Arc, Mutex};
use tracing::{info, warn}; use tracing::{info, warn};
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
struct sysInfo { struct SysInfo {
client: clientInfo, client: ClientInfo,
id: String, id: String,
// server: String, // server: String,
} }
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
struct clientInfo { struct ClientInfo {
id: i64, id: i64,
acc: String, acc: String,
name: String, name: String,
host: String, host: String,
} }
pub fn start_nats_online(client: async_nats::client::Client) { pub fn start_nats_online(client: async_nats::client::Client) {
let db: Arc<Mutex<HashMap<i64, clientInfo>>> = Arc::new(Mutex::new(HashMap::new())); let db: Arc<Mutex<HashMap<i64, ClientInfo>>> = Arc::new(Mutex::new(HashMap::new()));
{ {
let db = db.clone(); let db = db.clone();
let client = client.clone(); let client = client.clone();
@ -38,7 +38,7 @@ pub fn start_nats_online(client: async_nats::client::Client) {
while let Some(msg) = sub.next().await { while let Some(msg) = sub.next().await {
let s = String::from_utf8(msg.payload.to_vec()).unwrap(); let s = String::from_utf8(msg.payload.to_vec()).unwrap();
info!("{}", s); info!("{}", s);
let inf: sysInfo = serde_json::from_slice(&msg.payload.to_vec()).unwrap(); let inf: SysInfo = serde_json::from_slice(&msg.payload.to_vec()).unwrap();
info!("add {} {}", inf.client.id, inf.client.name); info!("add {} {}", inf.client.id, inf.client.name);
let mut db = db.lock().unwrap(); let mut db = db.lock().unwrap();
db.insert(inf.client.id, inf.client); db.insert(inf.client.id, inf.client);
@ -55,7 +55,7 @@ pub fn start_nats_online(client: async_nats::client::Client) {
.unwrap(); .unwrap();
while let Some(msg) = sub.next().await { while let Some(msg) = sub.next().await {
// let s = String::from_utf8(msg.payload.to_vec()).unwrap(); // let s = String::from_utf8(msg.payload.to_vec()).unwrap();
let inf: sysInfo = serde_json::from_slice(&msg.payload.to_vec()).unwrap(); let inf: SysInfo = serde_json::from_slice(&msg.payload.to_vec()).unwrap();
info!("remove {} {}", inf.client.id, inf.client.name); info!("remove {} {}", inf.client.id, inf.client.name);
let mut db = db.lock().unwrap(); let mut db = db.lock().unwrap();
db.remove(&inf.client.id); db.remove(&inf.client.id);
@ -66,14 +66,14 @@ pub fn start_nats_online(client: async_nats::client::Client) {
let mut sub = client.subscribe("sys.online".to_string()).await.unwrap(); let mut sub = client.subscribe("sys.online".to_string()).await.unwrap();
while let Some(msg) = sub.next().await { while let Some(msg) = sub.next().await {
// // let s = String::from_utf8(msg.payload.to_vec()).unwrap(); // // let s = String::from_utf8(msg.payload.to_vec()).unwrap();
// let inf: sysInfo = serde_json::from_slice(&msg.payload.to_vec()).unwrap(); // let inf: SysInfo = serde_json::from_slice(&msg.payload.to_vec()).unwrap();
// info!("remove {} {}", inf.client.id, inf.client.name); // info!("remove {} {}", inf.client.id, inf.client.name);
// let mut db = db.lock().unwrap(); // let mut db = db.lock().unwrap();
// db.remove(&inf.client.id); // db.remove(&inf.client.id);
if let Some(t) = msg.reply { if let Some(t) = msg.reply {
let d = { let d = {
let tmp = db.lock().unwrap(); let tmp = db.lock().unwrap();
let payload: Vec<clientInfo> = tmp.iter().map(|(_, c)| c.clone()).collect(); let payload: Vec<ClientInfo> = tmp.iter().map(|(_, c)| c.clone()).collect();
serde_json::to_string(&payload).unwrap() serde_json::to_string(&payload).unwrap()
}; };
match client.publish(t, d.into()).await { match client.publish(t, d.into()).await {

@ -5,12 +5,9 @@
// Distributed under terms of the Apache license. // Distributed under terms of the Apache license.
// //
use bytes::Bytes;
use actix_files as fs; use actix_files as fs;
use actix_web::{ use actix_web::{
dev::{self, Service}, dev::{self, Service},
get,
http::StatusCode, http::StatusCode,
middleware::{self, ErrorHandlerResponse, ErrorHandlers}, middleware::{self, ErrorHandlerResponse, ErrorHandlers},
web::{self}, web::{self},
@ -53,22 +50,7 @@ async fn main() -> Result<()> {
Ok(()) Ok(())
} }
async fn web(data: AppState) -> Result<()> { async fn web(data: AppState) -> Result<()> {
let client = match async_nats::ConnectOptions::new()
.nkey(data.nats_usr[1].clone())
.connect(data.info.nats_url.clone())
.await
{
Ok(r) => r,
Err(e) => {
info!("{}", e);
return Err(oab::Error::Unknown);
}
};
// libs::task::start_nats_online(client.clone()); // libs::task::start_nats_online(client.clone());
client
.publish("msg".to_string(), Bytes::from("asd"))
.await
.unwrap();
let url = data.server_url.clone(); let url = data.server_url.clone();
let dav = libs::fs::core(); let dav = libs::fs::core();
let serv = HttpServer::new(move || { let serv = HttpServer::new(move || {

@ -40,7 +40,6 @@ module.exports = configure(function(/* ctx */) {
'i18n', 'i18n',
'api', 'api',
'pack', 'pack',
'oaer',
], ],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css

File diff suppressed because one or more lines are too long

@ -1,24 +0,0 @@
/*
* oaer.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-16 21:20
* Distributed under terms of the MIT license.
*/
// import '@veypi/oaer'
import oaer from '@veypi/oaer'
import '@veypi/oaer/dist/index.css'
import cfg from 'src/cfg'
import bus from 'src/libs/bus'
import util from 'src/libs/util'
oaer.set({
token: util.getToken(),
host: cfg.host,
uuid: cfg.id,
})
bus.on('token', (t: any) => {
oaer.set({ token: t })
})

@ -1,18 +0,0 @@
<template>
<q-item class="flex items-center" v-ripple clickable tag="a" :href="link" :to="to">
<q-icon size="1.5rem" class="mr-2" :name="icon" />
<q-item-section>
<q-item-label>{{ title }}</q-item-label>
</q-item-section>
</q-item>
</template>
<script setup lang="ts">
import { MenuLink } from 'src/models'
withDefaults(defineProps<MenuLink>(), {
caption: '',
icon: '',
});
</script>

@ -1,37 +0,0 @@
<template>
<div>
<p>{{ title }}</p>
<ul>
<li v-for="todo in todos" :key="todo.id" @click="increment">
{{ todo.id }} - {{ todo.content }}
</li>
</ul>
<p>Count: {{ todoCount }} / {{ meta.totalCount }}</p>
<p>Active: {{ active ? 'yes' : 'no' }}</p>
<p>Clicks on todos: {{ clickCount }}</p>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { Todo, Meta } from './models';
interface Props {
title: string;
todos?: Todo[];
meta: Meta;
active: boolean;
}
const props = withDefaults(defineProps<Props>(), {
todos: () => [],
});
const clickCount = ref(0);
function increment() {
clickCount.value += 1;
return clickCount.value;
}
const todoCount = computed(() => props.todos.length);
</script>

@ -10,7 +10,7 @@
class="cursor-pointer rounded-full h-8 pr-4 flex items-center hover:bg-gray-100" @click="toggle"> class="cursor-pointer rounded-full h-8 pr-4 flex items-center hover:bg-gray-100" @click="toggle">
<q-icon class="transition-all mx-2" :class="[expand ? 'rotate-90' : <q-icon class="transition-all mx-2" :class="[expand ? 'rotate-90' :
'']" style="font-size: 24px;" :name="root.type === '']" style="font-size: 24px;" :name="root.type ===
'directory' ? 'v-caret-right' : 'v-file'"> </q-icon> 'directory' ? 'v-right' : 'v-file'"> </q-icon>
<div> <div>
{{ root.basename || '/' }} {{ root.basename || '/' }}
</div> </div>

@ -0,0 +1,48 @@
<!--
* tooltip.vue
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-23 21:49
* Distributed under terms of the MIT license.
-->
<template>
<div class="v-tooltip">
<slot></slot>
<div class="v-tooltip-text">
<slot name="text">
{{ text }}
</slot>
</div>
</div>
</template>
<script lang="ts" setup>
withDefaults(defineProps<{
text?: string
}>(),
{}
)
</script>
<style scoped>
.v-tooltip {
position: relative;
display: inline-block;
}
.v-tooltip .v-tooltip-text {
visibility: hidden;
visibility: hidden;
background-color: rgba(0, 0, 0, 0.8);
color: #fff;
text-align: center;
padding: 5px 5px;
border-radius: 6px;
position: absolute;
z-index: 1;
}
.v-tooltip:hover .v-tooltip-text {
visibility: visible;
}
</style>

@ -0,0 +1,11 @@
/*
* index.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-22 05:11
* Distributed under terms of the MIT license.
*/
import tschart from './tschart.vue'
export default tschart

@ -0,0 +1,71 @@
/*
* params.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-22 05:13
* Distributed under terms of the MIT license.
*/
import { ref } from 'vue'
let mode = ref(0)
let mode_label = ['近5分钟', '近1小时', '近24小时', '近7天', '近30天']
let change_mode = (m: number) => {
mode.value = m
let now = new Date()
switch (m) {
case 0: {
now.setMinutes(now.getMinutes() - 5)
params.value.start = now
params.value.step = "2s"
break
}
case 1: {
now.setHours(now.getHours() - 1)
params.value.start = now
params.value.step = "10s"
break
}
case 2: {
now.setHours(now.getHours() - 24)
params.value.start = now
params.value.step = "20s"
break
}
case 3: {
now.setHours(now.getHours() - 24 * 7)
params.value.start = now
params.value.step = "30s"
break
}
case 4: {
now.setHours(now.getHours() - 24 * 29)
params.value.start = now
params.value.step = "1h"
break
}
case 5: {
break
}
}
}
let params = ref<{ start: Date, end: Date, step: string }>({
start: new Date(),
end: new Date(),
step: '2s'
})
change_mode(0)
const set_delta = (start?: Date, end?: Date) => {
if (start) {
params.value.start = start
}
if (end) {
params.value.end = end
}
let delta = params.value.end.getTime() -
params.value.start.getTime()
console.log(delta)
}
export { params, change_mode, mode, mode_label }

@ -6,13 +6,22 @@
--> -->
<template> <template>
<div class="w-full h-full"> <div class="w-full h-full">
<div class="v-chart w-full h-full" ref="chartdom"></div> <div class="h-16 flex justify-start items-center">
<q-chip clickable :color="enable_sync ? 'primary' : ''" @click="enable_sync = !enable_sync">{{ enable_sync ? '关闭同步'
:
'开启同步' }}</q-chip>
<q-chip clickable :color="mode === k ? 'primary' : ''" v-for="(v, k) in mode_label" :key="k"
@click="change_mode(k)">{{
v }}</q-chip>
</div>
<div class="v-chart w-full" style="height: calc(100% - 4rem);" ref="chartdom"></div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as echart from 'echarts' import * as echart from 'echarts'
import api from 'src/boot/api'; import api from 'src/boot/api';
import { params, mode, change_mode, mode_label } from './params'
import { onMounted, onUnmounted, computed, ref, watch, markRaw } from 'vue'; import { onMounted, onUnmounted, computed, ref, watch, markRaw } from 'vue';
interface Item { interface Item {
@ -24,22 +33,14 @@ interface Item {
} }
let props = withDefaults(defineProps<{ let props = withDefaults(defineProps<{
item: Item, item: Item,
// start?: string, sync?: boolean,
// 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 count = 0
let timer = ref<any[]>([]) let timer = ref<any[]>([])
let enable_sync = ref(false)
let chartdom = ref() let chartdom = ref()
let options = ref<{ [key: string]: any }>({}) let options = ref<{ [key: string]: any }>({})
let chart: echart.ECharts = {} as any let chart: echart.ECharts = {} as any
@ -58,6 +59,9 @@ const init_chart = () => {
if (chart.clear) { if (chart.clear) {
chart.clear() chart.clear()
} }
timer.value.forEach(e => {
clearInterval(e)
})
options.value = { options.value = {
animationThreshold: 200, animationThreshold: 200,
tooltip: Object.assign({}, tooltip), tooltip: Object.assign({}, tooltip),
@ -70,23 +74,30 @@ const init_chart = () => {
xAxis: { xAxis: {
type: 'time', type: 'time',
}, },
dataZoom: [
{
type: 'slider',
xAxisIndex: [0],
filterMode: 'filter'
},
],
yAxis: {}, yAxis: {},
series: [] series: []
} }
if (props.item.valueFormatter) { if (props.item.valueFormatter) {
options.value.tooltip.valueFormatter = props.item.valueFormatter options.value.tooltip.valueFormatter = props.item.valueFormatter
} }
let tmp = {} as any let tmp = {
if (getparams.value.start) { start: params.value.start.toISOString(),
tmp.start = getparams.value.start() step: params.value.step,
} }
if (getparams.value.step) { let querys: string[] = Array.isArray(props.item.query) ? props.item.query :
tmp.step = getparams.value.step
}
let query: string[] = Array.isArray(props.item.query) ? props.item.query :
[props.item.query] [props.item.query]
for (let q = 0; q < query.length; q++) { let labels = Array.isArray(props.item.label) ? props.item.label :
api.tsdb.range(query[q], tmp).then(e => { [props.item.label]
for (let q = 0; q < querys.length; q++) {
let query = querys[q]
api.tsdb.range(query, tmp).then(e => {
if (e.status == 'success') { if (e.status == 'success') {
let data = e.data.result as any[] let data = e.data.result as any[]
if (data.length == 0) { if (data.length == 0) {
@ -96,19 +107,19 @@ const init_chart = () => {
let idx = options.value.series.length || 0 let idx = options.value.series.length || 0
data.forEach(d => { data.forEach(d => {
let name = props.item.name let name = props.item.name
if (typeof props.item.label === 'string') { let label = labels[q]
name = props.item.label if (typeof label === 'string') {
} else if (typeof props.item.label === 'function') { name = label
name = props.item.label(d.metric) } else if (typeof label === 'function') {
} else if (Array.isArray(props.item.label)) { name = label(d.metric)
name = props.item.label[q]
} }
options.value.series.push({ options.value.series.push({
name: name, name: name,
data: d.values.map((e: any) => data: d.values.map((e: any) =>
[e[0] * 1000, Number(e[1])]), [e[0] * 1000, Number(e[1])]),
metric: d.metric, metric: d.metric,
origin: query[q], metric_str: JSON.stringify(d.metric),
origin: query,
symbol: 'none', symbol: 'none',
smooth: true, smooth: true,
type: 'line', type: 'line',
@ -116,7 +127,7 @@ const init_chart = () => {
}) })
chart.setOption(options.value) chart.setOption(options.value)
let t = setInterval(() => { let t = setInterval(() => {
sync_chart(idx, query[q], count) sync_chart(idx, query, count)
}, 1000) }, 1000)
timer.value.push(t) timer.value.push(t)
} }
@ -125,16 +136,30 @@ const init_chart = () => {
// let query = props.query // let query = props.query
} }
const sync_chart = (idx: number, query: string, c: number) => { const sync_chart = (idx: number, query: string, c: number) => {
if (!enable_sync.value) {
return
}
api.tsdb.query(query).then(e => { api.tsdb.query(query).then(e => {
if (e.status == 'success') { if (e.status == 'success') {
let data = e.data.result as any[] let data = e.data.result as any[]
if (data.length == 0) { if (data.length == 0) {
console.warn('not get data') console.warn('not get data')
timer.value.forEach(e => {
clearInterval(e)
})
return return
} }
if (count === c) { if (count === c) {
data.forEach((d, i) => { data.forEach((d, i) => {
options.value.series[idx + i].data.push([d.value[0] * 1000, let sidx = idx + i
if (d.metric) {
let ti = options.value.series.findIndex((s: any) =>
query === s.origin && JSON.stringify(d.metric) === s.metric_str)
if (ti >= 0) {
sidx = ti
}
}
options.value.series[sidx].data.push([d.value[0] * 1000,
Number(d.value[1])]) Number(d.value[1])])
}) })
chart.setOption(options.value) chart.setOption(options.value)
@ -143,17 +168,22 @@ const sync_chart = (idx: number, query: string, c: number) => {
}) })
} }
watch(computed(() => props.item), q => { watch(computed(() => props.item), q => {
timer.value.forEach(e => {
clearInterval(e)
})
if (q) { if (q) {
init_chart() init_chart()
} }
}, { immediate: true }) })
watch(mode, q => {
init_chart()
})
onMounted(() => { onMounted(() => {
chart = markRaw(echart.init(chartdom.value)) enable_sync.value = props.sync
chart = markRaw(echart.init(chartdom.value, null, { renderer: 'svg' }))
init_chart()
}) })
onUnmounted(() => { onUnmounted(() => {
timer.value.forEach(e => { timer.value.forEach(e => {

@ -61,7 +61,20 @@ import { useRouter } from 'vue-router';
import Menu from './menu.vue' import Menu from './menu.vue'
import { useUserStore } from 'src/stores/user'; import { useUserStore } from 'src/stores/user';
import { OAer } from "@veypi/oaer"; import { OAer } from "@veypi/oaer";
import { util } from 'src/libs'; import { util, bus } from 'src/libs';
import oaer from '@veypi/oaer'
import '@veypi/oaer/dist/index.css'
import cfg from 'src/cfg'
oaer.set({
token: util.getToken(),
host: cfg.host,
uuid: cfg.id,
})
bus.on('token', (t: any) => {
oaer.set({ token: t })
})
const user = useUserStore() const user = useUserStore()
const router = useRouter() const router = useRouter()

@ -16,13 +16,17 @@
<script lang="ts" setup> <script lang="ts" setup>
import FsTree from 'src/components/FsTree.vue'; import FsTree from 'src/components/FsTree.vue';
import { oafs, fileProps } from '@veypi/oaer'; import { oafs, fileProps } from '@veypi/oaer';
import { onMounted, ref } from 'vue'; import { onMounted, ref, watch } from 'vue';
let root = ref({} as fileProps) let root = ref({} as fileProps)
watch(oafs.ready, e => {
if (e) {
oafs.dav().stat('/').then(e => {
root.value = e as fileProps
})
}
}, { immediate: true })
onMounted(() => { onMounted(() => {
oafs.dav().stat('/').then(e => {
root.value = e as fileProps
})
}) })
</script> </script>

@ -5,28 +5,118 @@
* Distributed under terms of the MIT license. * Distributed under terms of the MIT license.
--> -->
<template> <template>
<div class="flex flex-nowrap"> <div>
<div class="grow" style="height: calc(100%);"> <div v-if="id">
<tschart :item="querys[idx]"></tschart> <div class="text-2xl mb-4">
消息服务
<div class="float-right text-sm">{{ new Date(data.now).toLocaleString() }}</div>
</div>
<div class="">
<div class="w-full">ID: {{ id }}</div>
<div class="flex gap-8">
<div>CPU占用: {{ data.cpu }}%</div>
<div>内存占用: {{ (data.mem / 1024 / 1024).toFixed(2) }}M</div>
<div>连接数: {{ data.connections }}</div>
</div>
<div>发送: {{ (send_received[0] / 1024).toFixed(2) }} KB/s</div>
<div>收到: {{ (send_received[1] / 1024).toFixed(2) }} KB/s</div>
</div>
<div class="grid grid-cols-4 gap-4 mt-10" v-if="conns.length">
<div>ID</div>
<div>Name</div>
<div>运行时间</div>
<div>订阅主题</div>
<template v-for="c of conns" :key="c.cid">
<div>{{ c.cid }}</div>
<div>{{ c.name || '无' }}</div>
<div>{{ new Date(c.start).toLocaleString() }}</div>
<div>{{ c.subscriptions_list ?
c.subscriptions_list.sort().join(' ') : '' }}</div>
</template>
</div>
</div> </div>
<div class="flex flex-col gap-5"> <div class="flex flex-nowrap">
<q-chip :color="idx === i ? 'primary' : ''" class="select-none" v-for="(q, i) in querys" :key="i" @click="idx = i" <div class="grow" style="min-height: 50vh;">
clickable>{{ <tschart :item="querys[idx]"></tschart>
q.name }} </q-chip> </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> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import tschart from 'src/components/tschart.vue'; import { computed, ref, watch, onUnmounted } from 'vue';
import { ref } from 'vue'; import { nats } from '@veypi/oaer'
import api from 'src/boot/api'
const data = ref({} as any)
const conns = ref<any[]>([])
const id = computed(() => data.value.server_id)
const subs: any[] = []
const timer = ref()
let old_data = [0, 0]
const send_received = computed(() => {
if (!id.value) {
return [0, 0]
}
let os = data.value.out_bytes
let or = data.value.in_bytes
let res = [os - old_data[0], or - old_data[1]]
old_data = [os, or]
return res
})
watch(id, (_) => {
timer.value = setInterval(() => {
api.nats.general().then(e => {
data.value = e
})
api.nats.conns().then(e => {
conns.value = e.connections
})
// nats.request('$SYS.REQ.SERVER.PING').then((m) => {
// data.value = JSON.parse(m)
// })
}, 1000)
})
watch(computed(() => nats.ready.value), e => {
if (e) {
api.nats.general().then(e => {
old_data = [e.out_bytes, e.in_bytes]
data.value = e
})
}
}, { immediate: true })
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
}
for (let i of subs) {
i.unsubscribe()
}
})
import tschart from 'src/components/tschart';
const idx = ref(0) const idx = ref(0)
const querys = ref([ const querys = ref<{
name: string, query: string[] | string, label?: any,
valueFormatter?: any
}[]>([
{ {
name: 'cpu占用', name: 'cpu占用',
query: `100 - avg (irate(node_cpu_seconds_total{mode="idle"}[3s])) // query: `100 - avg (irate(node_cpu_seconds_total{mode="idle"}[3s]))
by(id) * 100`, // by(id) * 100`,
query: `avg by
(id)(irate(node_cpu_seconds_total{mode=~"sytem|user|iowait|irq|softirq|nice|steal|guest"}[3s]))
* 100`,
label: (d: any) => d.id as string, label: (d: any) => d.id as string,
valueFormatter: (value: number) => value.toFixed(2) + "%", valueFormatter: (value: number) => value.toFixed(2) + "%",
}, },
@ -40,28 +130,59 @@ const querys = ref([
valueFormatter: (value: number) => value.toFixed(2) + "%", valueFormatter: (value: number) => value.toFixed(2) + "%",
}, },
{ {
name: 'linux 内存', name: '磁盘',
query: [ query: `(1 - avg(node_filesystem_avail_bytes /
`(node_memory_Buffers_bytes + node_memory_Cached_bytes + node_filesystem_size_bytes[3s]) by (device, id)) * 100 `,
node_memory_MemFree_bytes) / 1024 / 1024 / 1024`, label: (d: any) => `${d.id}: ${d.device}` as string,
`node_memory_MemTotal_bytes / 1024 /1024 / 1024` valueFormatter: (value: number) => value.toFixed(2) + "%",
],
label: ['使用内存', '总内存'],
valueFormatter: (value: number) => value.toFixed(2) + "GB",
}, },
{ {
name: 'Mac cpu频率', name: '磁盘IOPS',
query: 'node_cpu_seconds_total', query: [
label: (d: any) => `cpu: ${d.cpu} mode: ${d.mode}` as string `sum by (id) (rate(node_disk_reads_completed_total[3s]))`,
`sum by (id) (rate(node_disk_writes_completed_total[3s]))`,
],
label: [
(d: any) => `${d.id}`,
(d: any) => `${d.id}`,
],
}, },
{ {
name: 'mem', name: '网络带宽',
query: 'node_memory_free_bytes / 1024 / 1024 / 1024' query: [
`sum by(id)(irate(node_network_receive_bytes_total{device!~"bond.*?|lo"}[3s])) / 1048576`,
`sum by(id)(irate(node_network_transmit_bytes_total{device!~"bond.*?|lo"}[3s])) / 1048576`
],
label: [
(d: any) => `${d.id} 下行`,
(d: any) => `${d.id} 上行`,
],
valueFormatter: (value: number) => value.toFixed(2) + "MB/s",
}, },
{ {
name: 'Mac swap', name: '内存',
query: 'node_memory_swap_used_bytes / 1024 / 1024 ' query: [
`(node_memory_Buffers_bytes + node_memory_Cached_bytes +
node_memory_MemFree_bytes) / 1024 / 1024 / 1024`,
`node_memory_MemTotal_bytes / 1024 /1024 / 1024`
],
label: [(d: any) => `${d.id}使用内`, (d: any) => `${d.id}总内存`],
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> </script>

Loading…
Cancel
Save