mirror of https://github.com/veypi/OneAuth.git
stats appcard page
parent
2c0326d75d
commit
127f5ed463
@ -0,0 +1,77 @@
|
|||||||
|
<!--
|
||||||
|
* appcard.vue
|
||||||
|
* Copyright (C) 2024 veypi <i@veypi.com>
|
||||||
|
* 2024-06-07 16:48
|
||||||
|
* Distributed under terms of the MIT license.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="core rounded-2xl p-3">
|
||||||
|
<div class="grid gap-4 grid-cols-5">
|
||||||
|
<div class="col-span-2">
|
||||||
|
<img :src="core.icon">
|
||||||
|
</div>
|
||||||
|
<div class="col-span-3 grid grid-cols-1 items-center text-left">
|
||||||
|
<div class="truncate h-10 flex items-center text-xl italic font-bold">
|
||||||
|
{{ core.name }}
|
||||||
|
</div>
|
||||||
|
<span class="truncate">{{ }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import msg from "@veypi/msg";
|
||||||
|
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
|
||||||
|
let props = withDefaults(defineProps<{
|
||||||
|
core: modelsApp,
|
||||||
|
is_part: boolean
|
||||||
|
}>(),
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
const u = useUserStore()
|
||||||
|
|
||||||
|
function Go() {
|
||||||
|
if (props.is_part) {
|
||||||
|
router.push({ name: "app.home", params: { id: props.core.id } });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// $q.dialog({
|
||||||
|
// title: '确认',
|
||||||
|
// message: '是否确定申请加入应用 ' + props.core.name,
|
||||||
|
// cancel: true,
|
||||||
|
// }).onOk(() => {
|
||||||
|
api.app.user(props.core.id).add(u.id).then(e => {
|
||||||
|
switch (e.status) {
|
||||||
|
case AUStatus.OK:
|
||||||
|
msg.Info('加入成功')
|
||||||
|
router.push({ name: "app.home", params: { id: props.core.id } });
|
||||||
|
return;
|
||||||
|
case AUStatus.Applying:
|
||||||
|
msg.Info("请等待管理员审批进入");
|
||||||
|
return;
|
||||||
|
case AUStatus.Deny:
|
||||||
|
msg.Warn("进入申请未通过");
|
||||||
|
return;
|
||||||
|
case AUStatus.Disabled:
|
||||||
|
msg.Warn("已被禁止使用");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}).catch(e => {
|
||||||
|
msg.Warn("加入失败" + e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.core {
|
||||||
|
width: 256px;
|
||||||
|
background: rgba(146, 145, 145, 0.1);
|
||||||
|
}
|
||||||
|
</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,74 @@
|
|||||||
|
/*
|
||||||
|
* params.ts
|
||||||
|
* Copyright (C) 2023 veypi <i@veypi.com>
|
||||||
|
* 2023-10-22 05:13
|
||||||
|
* Distributed under terms of the MIT license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
const use_params = () => {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
return { params, change_mode, mode, mode_label }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default use_params
|
@ -0,0 +1,216 @@
|
|||||||
|
<!--
|
||||||
|
* 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 v-if="enable_mode" class="h-16 flex justify-start items-center">
|
||||||
|
<div :color="enable_sync ? 'primary' : ''" @click="enable_sync = !enable_sync">{{ enable_sync ? '关闭同步'
|
||||||
|
:
|
||||||
|
'开启同步' }}</div>
|
||||||
|
<div :color="mode === k ? 'primary' : ''" v-for="(v, k) in mode_label" :key="k" @click="change_mode(k)">{{
|
||||||
|
v }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="v-chart w-full" :style="{
|
||||||
|
height:
|
||||||
|
enable_mode ? 'calc(100% - 4rem)' : '100%'
|
||||||
|
}" ref="chartdom"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import * as echart from 'echarts'
|
||||||
|
import use_params from './params'
|
||||||
|
import { onMounted, onUnmounted, computed, ref, watch, markRaw } from 'vue';
|
||||||
|
|
||||||
|
let { params, mode, change_mode, mode_label } = use_params()
|
||||||
|
interface Item {
|
||||||
|
name: string
|
||||||
|
query: string | string[]
|
||||||
|
valueFormatter?: (s: number) => string
|
||||||
|
label?: string | string[] | ((s: any) => string)
|
||||||
|
}
|
||||||
|
let props = withDefaults(defineProps<{
|
||||||
|
item: Item,
|
||||||
|
sync?: boolean,
|
||||||
|
enable_zoom?: boolean,
|
||||||
|
time_mode?: number,
|
||||||
|
enable_mode?: boolean,
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
}
|
||||||
|
)
|
||||||
|
let count = 0
|
||||||
|
let timer = ref<any[]>([])
|
||||||
|
let enable_sync = ref(false)
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
timer.value.forEach(e => {
|
||||||
|
clearInterval(e)
|
||||||
|
})
|
||||||
|
options.value = {
|
||||||
|
title: { text: props.item.name, x: 'center' },
|
||||||
|
animationThreshold: 200,
|
||||||
|
tooltip: Object.assign({}, tooltip),
|
||||||
|
axisPointer: {
|
||||||
|
link: { xAxisIndex: 'all' },
|
||||||
|
label: {
|
||||||
|
backgroundColor: '#777'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'time',
|
||||||
|
},
|
||||||
|
yAxis: {},
|
||||||
|
series: []
|
||||||
|
}
|
||||||
|
if (props.enable_zoom) {
|
||||||
|
options.value.dataZoom = [
|
||||||
|
{
|
||||||
|
type: 'slider',
|
||||||
|
xAxisIndex: [0],
|
||||||
|
filterMode: 'filter'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (props.item.valueFormatter) {
|
||||||
|
options.value.tooltip.valueFormatter = props.item.valueFormatter
|
||||||
|
}
|
||||||
|
let tmp = {
|
||||||
|
start: params.value.start.toISOString(),
|
||||||
|
step: params.value.step,
|
||||||
|
}
|
||||||
|
let querys: string[] = Array.isArray(props.item.query) ? props.item.query :
|
||||||
|
[props.item.query]
|
||||||
|
let labels = Array.isArray(props.item.label) ? props.item.label :
|
||||||
|
[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') {
|
||||||
|
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
|
||||||
|
let label = labels[q]
|
||||||
|
if (typeof label === 'string') {
|
||||||
|
name = label
|
||||||
|
} else if (typeof label === 'function') {
|
||||||
|
name = label(d.metric)
|
||||||
|
}
|
||||||
|
options.value.series.push({
|
||||||
|
name: name,
|
||||||
|
data: d.values.map((e: any) =>
|
||||||
|
[e[0] * 1000, Number(e[1])]),
|
||||||
|
metric: d.metric,
|
||||||
|
metric_str: JSON.stringify(d.metric),
|
||||||
|
origin: query,
|
||||||
|
symbol: 'none',
|
||||||
|
smooth: true,
|
||||||
|
type: 'line',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
chart.setOption(options.value)
|
||||||
|
let t = setInterval(() => {
|
||||||
|
sync_chart(idx, query, count)
|
||||||
|
}, 1000)
|
||||||
|
timer.value.push(t)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// let query = props.query
|
||||||
|
}
|
||||||
|
const sync_chart = (idx: number, query: string, c: number) => {
|
||||||
|
if (!enable_sync.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
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')
|
||||||
|
timer.value.forEach(e => {
|
||||||
|
clearInterval(e)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (count === c) {
|
||||||
|
data.forEach((d, i) => {
|
||||||
|
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])])
|
||||||
|
})
|
||||||
|
chart.setOption(options.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
watch(computed(() => props.item), q => {
|
||||||
|
if (q) {
|
||||||
|
init_chart()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(mode, q => {
|
||||||
|
init_chart()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
enable_sync.value = props.sync
|
||||||
|
if (props.time_mode) {
|
||||||
|
change_mode(props.time_mode)
|
||||||
|
}
|
||||||
|
chart = markRaw(echart.init(chartdom.value, null, { renderer: 'svg' }))
|
||||||
|
init_chart()
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
timer.value.forEach(e => {
|
||||||
|
clearInterval(e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.v-chart {
|
||||||
|
min-width: 20rem;
|
||||||
|
min-height: 15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v-echarts-tooltip {
|
||||||
|
/* height: 5rem; */
|
||||||
|
/* width: 10rem; */
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,89 @@
|
|||||||
|
|
||||||
|
<!--
|
||||||
|
* stats.vue
|
||||||
|
* Copyright (C) 2023 veypi <i@veypi.com>
|
||||||
|
* 2023-10-20 22:56
|
||||||
|
* Distributed under terms of the MIT license.
|
||||||
|
-->
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="page-h1">
|
||||||
|
服务
|
||||||
|
</div>
|
||||||
|
<div class="w-40 text-center py-4 start_card">
|
||||||
|
<div class="text-3xl"> 已运行 </div>
|
||||||
|
<div class="text-2xl mt-2">
|
||||||
|
{{ start_time }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-nowrap" style="">
|
||||||
|
<div class="w-1/2">
|
||||||
|
<Tschart :item="querys[0]" :time_mode="1"></Tschart>
|
||||||
|
</div>
|
||||||
|
<div class="w-1/2">
|
||||||
|
<Tschart :item="querys[1]" :time_mode="1"></Tschart>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onUnmounted, onMounted } from 'vue';
|
||||||
|
// import tschart from 'src/components/tschart';
|
||||||
|
|
||||||
|
const start_time = ref('')
|
||||||
|
const timer = ref()
|
||||||
|
const querys = ref<{
|
||||||
|
name: string, query: string[] | string, label?: any,
|
||||||
|
valueFormatter?: any
|
||||||
|
}[]>([
|
||||||
|
{
|
||||||
|
name: 'cpu',
|
||||||
|
query: `srv_cpu{i='oa'}`,
|
||||||
|
label: 'cpu',
|
||||||
|
valueFormatter: (value: number) => value.toFixed(2) + "%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '内存',
|
||||||
|
query: `srv_mem{i='oa'} / 1048576`,
|
||||||
|
label: '内存',
|
||||||
|
valueFormatter: (value: number) => value.toFixed(2) + "MB",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
api.tsdb.query('srv_start{i="oa"}').then(e => {
|
||||||
|
if (e.data.result.length) {
|
||||||
|
let s = Number(e.data.result[0].value[1])
|
||||||
|
if (s < 60) {
|
||||||
|
start_time.value = s + ' 秒'
|
||||||
|
} else if (s < 3600) {
|
||||||
|
start_time.value = (s / 60).toFixed(1) + ' 分钟'
|
||||||
|
} else if (s < 3600 * 24) {
|
||||||
|
start_time.value = (s / 60 / 60).toFixed(1) + ' 小时'
|
||||||
|
} else {
|
||||||
|
start_time.value = (s / 60 / 60 / 24).toFixed(1) + ' 天'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (timer.value) {
|
||||||
|
clearInterval(timer.value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.start_card {
|
||||||
|
border: 1px solid var(--color-primary);
|
||||||
|
|
||||||
|
:first-child {
|
||||||
|
color: var(--color-primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
:nth-child(2) {}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue