Compare commits

2 Commits

Author SHA1 Message Date
akrike
ca849de37f ui update 2025-09-03 22:06:35 +08:00
akrike
e14df1fd99 edit token check 2025-09-03 22:06:30 +08:00
7 changed files with 298 additions and 29 deletions

View File

@@ -1,7 +1,6 @@
package middle
import (
"errors"
"slices"
"strings"
@@ -46,36 +45,19 @@ func CheckToken() gin.HandlerFunc {
return strings.HasPrefix(c.Request.URL.Path, s)
}) {
var token string
if c.Request.Header.Get("token") != "" {
token = c.Request.Header.Get("token")
if c.Request.Header.Get("Authorization") != "" {
token = strings.TrimPrefix(c.Request.Header.Get("Authorization"), "bearer ")
} else {
token = c.Query("token")
}
if _, err := utils.VerifyToken(token); err != nil {
if mc, err := utils.VerifyToken(token); err != nil {
rErr(c, -2, "token校验失败", err)
return
}
if username, err := getUser(c); err != nil {
rErr(c, -1, "无法获取user信息", err)
} else {
c.Set(eum.CtxUserName, username)
c.Set(eum.CtxRole, repository.UserRepository.GetUserByName(username).Role)
c.Set(eum.CtxUserName, mc.Username)
c.Set(eum.CtxRole, repository.UserRepository.GetUserByName(mc.Username).Role)
}
}
c.Next()
}
}
func getUser(ctx *gin.Context) (string, error) {
var token string
if ctx.Request.Header.Get("token") != "" {
token = ctx.Request.Header.Get("token")
} else {
token = ctx.Query("token")
}
if mc, err := utils.VerifyToken(token); err == nil && mc != nil {
return mc.Username, nil
} else {
return "", errors.Join(errors.New("用户信息获取失败"), err)
}
}

View File

@@ -40,7 +40,7 @@ class RequestHttp {
this.service.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token") || "";
config.headers.Authorization = token;
config.headers.Authorization = "bearer " + token;
config.url = "/api" + config.url;
return config;
},

View File

@@ -1,11 +1,12 @@
import { ProcessItem } from "../types/process/process";
import api from "./api";
export function getProcessList() {
return api.get("/process", undefined).then((res) => res);
return api.get<ProcessItem[]>("/process", undefined).then((res) => res);
}
export function getProcessListWait() {
return api.get("/process/wait", undefined).then((res) => res);
return api.get<ProcessItem[]>("/process/wait", undefined).then((res) => res);
}
export function killProcessAll(uuid) {

View File

@@ -0,0 +1,206 @@
<script setup lang="ts">
import { ProcessItem } from "~/src/types/process/process";
import { init } from "echarts";
const initEChart = () => {
props.data.usage.cpu = (props.data.usage.cpu ?? [0, 0]).map((num) =>
parseFloat(num.toFixed(2))
);
props.data.usage.mem = (props.data.usage.mem ?? [0, 0]).map((num) =>
parseFloat(num.toFixed(2))
);
const cpu = props.data.usage.cpu[props.data.usage.cpu.length - 1] ?? "-";
const mem = props.data.usage.mem[props.data.usage.mem.length - 1] ?? "-";
var myChart = init(document.getElementById("echarts" + props.index));
var option = {
tooltip: {
trigger: "axis",
},
legend: {
data: ["CPU", "内存"],
},
animationDuration: 2000,
grid: {
left: "3%",
right: "4%",
bottom: "3%",
containLabel: true,
},
xAxis: {
type: "category",
boundaryGap: false,
show: false,
data: props.data.usage.time,
},
yAxis: [
{
type: "value",
name: " CPU(" + cpu + "%)",
min: 0, // 设置CPU的y轴最小值为10
max: props.data.usage.cpuCapacity,
minInterval: 0.1,
splitLine: {
show: false,
},
axisLine: { show: false },
axisTick: { show: false },
},
{
type: "value",
name: " 内存(" + mem + "MB)",
max: parseFloat((props.data.usage.memCapacity / 1024).toFixed(2)),
axisLine: { show: false },
axisTick: { show: false },
splitLine: { show: false },
},
],
series: [
{
name: "CPU",
type: "line",
data: props.data.usage.cpu,
yAxisIndex: 0,
showSymbol: false,
lineStyle: {
color: "#4ee5b9",
},
itemStyle: {
color: "#4ee5b9",
},
},
{
name: "内存",
type: "line",
data: props.data.usage.mem,
yAxisIndex: 1,
showSymbol: false,
lineStyle: {
color: "#ffe17e",
},
itemStyle: {
color: "#ffe17e",
},
},
],
};
if (props.data.cgroupEnable) {
if (props.data.cpuLimit) {
(option.series as any).push({
name: "CPU限制%",
type: "line",
yAxisIndex: 0,
data: new Array(props.data.usage.time!.length).fill(
props.data.cpuLimit
),
lineStyle: {
type: "dashed",
color: "#4ee5b9",
},
showSymbol: false,
});
}
if (props.data.memoryLimit) {
(option.series as any).push({
name: "内存限制MB",
type: "line",
yAxisIndex: 1,
data: new Array(props.data.usage.time!.length).fill(
props.data.memoryLimit
),
lineStyle: {
type: "dashed",
color: "#ffe17e",
},
showSymbol: false,
});
}
}
console.log(option);
myChart.setOption(option);
};
const buttons = [
{ label: "按钮1", action: () => console.log("按钮1点击") },
{ label: "按钮2", action: () => console.log("按钮2点击") },
{ label: "按钮3", action: () => console.log("按钮3点击") },
];
onMounted(() => {
initEChart();
});
const props = defineProps<{
data: ProcessItem;
index: Number;
}>();
</script>
<template>
<div class="chart-container">
<!-- 顶部进程名字 + 菜单 -->
<div class="header">
<div class="top-left">{{ props.data.name }}</div>
<div class="top-right">
<button @click="">菜单</button>
</div>
</div>
<!-- 中间ECharts -->
<div :id="'echarts' + props.index" class="chart"></div>
<!-- 底部按钮组 + 时间 -->
<div class="footer">
<div class="bottom-left">
<button v-for="(btn, idx) in buttons" :key="idx" @click="">
{{ btn.label }}
</button>
</div>
<div class="bottom-right">{{ props.data.startTime }}</div>
</div>
</div>
</template>
<style scoped>
.chart-container {
display: flex;
flex-direction: column;
width: 100%;
height: 260px; /* 可根据实际容器调整 */
background: #fff;
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
}
/* 顶部 header */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 10px;
font-weight: bold;
height: 30px; /* 顶部固定高度 */
}
/* 中间图表自适应 */
.chart {
flex: 1; /* 占满剩余空间 */
width: 100%;
}
/* 底部 footer */
.footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 10px;
height: 40px; /* 底部固定高度 */
}
.bottom-left {
display: flex;
gap: 5px;
}
.bottom-right {
text-align: right;
}
</style>

View File

@@ -22,12 +22,13 @@ import i18n from "./plugins/i18n";
import Vue3Lottie from "vue3-lottie";
import { autoAnimatePlugin } from '@formkit/auto-animate/vue'
import permission from "./directives/permission"
import * as echarts from 'echarts';
const pinia = createPinia();
pinia.use(piniaPersist);
const app = createApp(App);
app.config.globalProperties.$echarts = echarts;
app.directive('permission', permission);
app.use(router);
app.use(PerfectScrollbarPlugin);

View File

@@ -0,0 +1,34 @@
export interface ProcessItem {
name: string;
uuid: number;
startTime: Date;
user: string;
usage: Usage;
state: State;
termType: TermType;
cgroupEnable: boolean;
memoryLimit: number | null;
cpuLimit: number | null;
}
export interface State {
state: number;
info: Info;
}
export enum Info {
Empty = "",
= "重启次数异常",
}
export enum TermType {
Pty = "pty",
}
export interface Usage {
cpuCapacity: number;
memCapacity: number;
cpu: number[] | null;
mem: number[] | null;
time: string[] | null;
}

View File

@@ -1,3 +1,48 @@
<script setup lang="ts"></script>
<template>
<v-container>
<div class="flex-grid">
<div v-for="(i, v) in processData" class="responsive-box">
<ProcessCard :data="i" :index="v"></ProcessCard>
</div>
</div>
</v-container>
</template>
<template></template>
<script setup lang="ts">
import ProcessCard from "@/components/process/ProcessCard.vue";
import { getProcessList } from "~/src/api/process";
import { ProcessItem } from "~/src/types/process/process";
const processData = ref<ProcessItem[]>();
const initProcessData = () => {
getProcessList().then((e) => {
processData.value = e.data!;
console.log(e.data);
});
};
onMounted(() => {
initProcessData();
});
</script>
<style scoped>
.flex-grid {
display: flex;
flex-wrap: wrap; /* 自动换行 */
justify-content: space-between; /* 两边与中间间距均匀 */
gap: 50px; /* 每个 div 之间的间距 */
}
.responsive-box {
flex: 1 1 300px; /* 最小宽度 500px */
min-width: 300px; /* 强制最小宽度 */
max-width: 100%; /* 不超过容器宽度 */
background: #f5f5f5;
padding: 16px;
border-radius: 12px;
text-align: center;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
</style>