mirror of
https://github.com/lzh-1625/go_process_manager.git
synced 2025-09-26 20:11:20 +08:00
Compare commits
2 Commits
2e37eae3ca
...
ca849de37f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ca849de37f | ||
![]() |
e14df1fd99 |
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
},
|
||||
|
@@ -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) {
|
||||
|
206
resources/src/components/process/ProcessCard.vue
Normal file
206
resources/src/components/process/ProcessCard.vue
Normal 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>
|
@@ -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);
|
||||
|
34
resources/src/types/process/process.ts
Normal file
34
resources/src/types/process/process.ts
Normal 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;
|
||||
}
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user