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