mirror of
https://github.com/lzh-1625/go_process_manager.git
synced 2025-10-07 08:51:06 +08:00
update
This commit is contained in:
@@ -71,7 +71,7 @@ func (w *wsApi) WebsocketHandle(ctx *gin.Context, req model.WebsocketHandleReq)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
log.Logger.Infow("ws连接成功")
|
log.Logger.Infow("ws连接成功")
|
||||||
|
|
||||||
wsCtx, cancel := context.WithCancel(context.Background())
|
wsCtx, cancel := context.WithCancel(context.Background())
|
||||||
@@ -80,7 +80,9 @@ func (w *wsApi) WebsocketHandle(ctx *gin.Context, req model.WebsocketHandleReq)
|
|||||||
CancelFunc: cancel,
|
CancelFunc: cancel,
|
||||||
wsLock: sync.Mutex{},
|
wsLock: sync.Mutex{},
|
||||||
}
|
}
|
||||||
proc.ReadCache(wci)
|
if err := proc.ReadCache(wci); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if proc.State.State == eum.ProcessStateRunning {
|
if proc.State.State == eum.ProcessStateRunning {
|
||||||
proc.SetTerminalSize(req.Cols, req.Rows)
|
proc.SetTerminalSize(req.Cols, req.Rows)
|
||||||
w.startWsConnect(wci, cancel, proc, hasOprPermission(ctx, req.Uuid, eum.OperationTerminalWrite))
|
w.startWsConnect(wci, cancel, proc, hasOprPermission(ctx, req.Uuid, eum.OperationTerminalWrite))
|
||||||
@@ -99,7 +101,6 @@ func (w *wsApi) WebsocketHandle(ctx *gin.Context, req model.WebsocketHandleReq)
|
|||||||
case <-wsCtx.Done():
|
case <-wsCtx.Done():
|
||||||
log.Logger.Infow("ws连接断开", "操作类型", "tcp连接建立已被关闭")
|
log.Logger.Infow("ws连接断开", "操作类型", "tcp连接建立已被关闭")
|
||||||
}
|
}
|
||||||
conn.Close()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +130,7 @@ func (w *wsApi) WebsocketShareHandle(ctx *gin.Context, req model.WebsocketHandle
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
log.Logger.Infow("ws连接成功")
|
log.Logger.Infow("ws连接成功")
|
||||||
data.UpdatedAt = time.Now()
|
data.UpdatedAt = time.Now()
|
||||||
repository.WsShare.Edit(data)
|
repository.WsShare.Edit(data)
|
||||||
@@ -141,7 +142,9 @@ func (w *wsApi) WebsocketShareHandle(ctx *gin.Context, req model.WebsocketHandle
|
|||||||
CancelFunc: cancel,
|
CancelFunc: cancel,
|
||||||
wsLock: sync.Mutex{},
|
wsLock: sync.Mutex{},
|
||||||
}
|
}
|
||||||
proc.ReadCache(wci)
|
if err := proc.ReadCache(wci); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
w.startWsConnect(wci, cancel, proc, data.Write)
|
w.startWsConnect(wci, cancel, proc, data.Write)
|
||||||
proc.AddConn(guestName, wci)
|
proc.AddConn(guestName, wci)
|
||||||
defer proc.DeleteConn(guestName)
|
defer proc.DeleteConn(guestName)
|
||||||
@@ -159,7 +162,6 @@ func (w *wsApi) WebsocketShareHandle(ctx *gin.Context, req model.WebsocketHandle
|
|||||||
case <-time.After(time.Until(data.ExpireTime)):
|
case <-time.After(time.Until(data.ExpireTime)):
|
||||||
log.Logger.Infow("ws连接断开", "操作类型", "分享时间已结束")
|
log.Logger.Infow("ws连接断开", "操作类型", "分享时间已结束")
|
||||||
}
|
}
|
||||||
conn.Close()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Process interface {
|
type Process interface {
|
||||||
ReadCache(ConnectInstance)
|
ReadCache(ConnectInstance) error
|
||||||
Write(string) error
|
Write(string) error
|
||||||
WriteBytes([]byte) error
|
WriteBytes([]byte) error
|
||||||
readInit()
|
readInit()
|
||||||
|
@@ -2,6 +2,7 @@ package logic
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -113,10 +114,12 @@ func (p *ProcessPty) readInit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessPty) ReadCache(ws ConnectInstance) {
|
func (p *ProcessPty) ReadCache(ws ConnectInstance) error {
|
||||||
if p.cacheBytesBuf != nil {
|
if p.cacheBytesBuf == nil {
|
||||||
ws.Write(p.cacheBytesBuf.Bytes())
|
return errors.New("cache is null")
|
||||||
}
|
}
|
||||||
|
ws.Write(p.cacheBytesBuf.Bytes())
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessPty) bufHanle(b []byte) {
|
func (p *ProcessPty) bufHanle(b []byte) {
|
||||||
|
@@ -122,8 +122,12 @@ func (p *ProcessPty) readInit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessPty) ReadCache(ws ConnectInstance) {
|
func (p *ProcessPty) ReadCache(ws ConnectInstance) error {
|
||||||
|
if p.cacheBytesBuf == nil {
|
||||||
|
return errors.New("cache is null")
|
||||||
|
}
|
||||||
ws.Write(p.cacheBytesBuf.Bytes())
|
ws.Write(p.cacheBytesBuf.Bytes())
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessPty) bufHanle(b []byte) {
|
func (p *ProcessPty) bufHanle(b []byte) {
|
||||||
|
@@ -87,10 +87,14 @@ func (p *ProcessStd) doOnInit() {
|
|||||||
p.cacheLine = make([]string, config.CF.ProcessMsgCacheLinesLimit)
|
p.cacheLine = make([]string, config.CF.ProcessMsgCacheLinesLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessStd) ReadCache(ws ConnectInstance) {
|
func (p *ProcessStd) ReadCache(ws ConnectInstance) error {
|
||||||
|
if len(p.cacheLine) == 0 {
|
||||||
|
return errors.New("cache is null")
|
||||||
|
}
|
||||||
for _, line := range p.cacheLine {
|
for _, line := range p.cacheLine {
|
||||||
ws.WriteString(line)
|
ws.WriteString(line)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProcessStd) doOnKilled() {
|
func (p *ProcessStd) doOnKilled() {
|
||||||
|
@@ -9,12 +9,12 @@ export function getProcessListWait() {
|
|||||||
return api.get<ProcessItem[]>("/process/wait", undefined).then((res) => res);
|
return api.get<ProcessItem[]>("/process/wait", undefined).then((res) => res);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function killProcessAll(uuid) {
|
export function killProcessAll() {
|
||||||
return api.delete("/process/all", { uuid }).then((res) => res);
|
return api.delete("/process/all", { }).then((res) => res);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startProcessAll(uuid) {
|
export function startProcessAll() {
|
||||||
return api.put("/process/all", { uuid }).then((res) => res);
|
return api.put("/process/all", { }).then((res) => res);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function killProcess(uuid) {
|
export function killProcess(uuid) {
|
||||||
|
45
resources/src/components/ConfirmButton.vue
Normal file
45
resources/src/components/ConfirmButton.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- 触发按钮 -->
|
||||||
|
<v-btn :color="color" size="small" variant="tonal" @click="dialog = true">
|
||||||
|
<slot>{{ label }}</slot>
|
||||||
|
</v-btn>
|
||||||
|
|
||||||
|
<!-- 确认弹窗 -->
|
||||||
|
<v-dialog v-model="dialog" max-width="400">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h6">{{ title }}</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
{{ message }}
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn variant="text" @click="dialog = false">取消</v-btn>
|
||||||
|
<v-btn :color="color" variant="flat" @click="confirm"> 确认 </v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
label: { type: String, default: "操作" },
|
||||||
|
title: { type: String, default: "确认" },
|
||||||
|
message: { type: String, default: "确定要执行此操作吗?" },
|
||||||
|
color: { type: String, default: "primary" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const emits = defineEmits(["confirm"]);
|
||||||
|
|
||||||
|
const dialog = ref(false);
|
||||||
|
|
||||||
|
const confirm = () => {
|
||||||
|
dialog.value = false;
|
||||||
|
emits("confirm");
|
||||||
|
};
|
||||||
|
</script>
|
@@ -2,7 +2,12 @@
|
|||||||
import { ProcessItem } from "~/src/types/process/process";
|
import { ProcessItem } from "~/src/types/process/process";
|
||||||
import { init } from "echarts";
|
import { init } from "echarts";
|
||||||
import TerminalPty from "./TerminalPty.vue";
|
import TerminalPty from "./TerminalPty.vue";
|
||||||
import { killProcess, startProcess } from "~/src/api/process";
|
import {
|
||||||
|
deleteProcessConfig,
|
||||||
|
getContorl,
|
||||||
|
killProcess,
|
||||||
|
startProcess,
|
||||||
|
} from "~/src/api/process";
|
||||||
import { useSnackbarStore } from "~/src/stores/snackbarStore";
|
import { useSnackbarStore } from "~/src/stores/snackbarStore";
|
||||||
import ProcessConfig from "./ProcessConfig.vue";
|
import ProcessConfig from "./ProcessConfig.vue";
|
||||||
let chartInstance;
|
let chartInstance;
|
||||||
@@ -17,7 +22,7 @@ const initEChart = () => {
|
|||||||
);
|
);
|
||||||
const cpu = props.data.usage.cpu[props.data.usage.cpu.length - 1] ?? "-";
|
const cpu = props.data.usage.cpu[props.data.usage.cpu.length - 1] ?? "-";
|
||||||
const mem = props.data.usage.mem[props.data.usage.mem.length - 1] ?? "-";
|
const mem = props.data.usage.mem[props.data.usage.mem.length - 1] ?? "-";
|
||||||
var myChart = init(document.getElementById("echarts" + props.index));
|
var myChart = init(document.getElementById("echarts" + props.data.uuid));
|
||||||
var option = {
|
var option = {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: "axis",
|
||||||
@@ -95,7 +100,7 @@ const initEChart = () => {
|
|||||||
name: "CPU限制(%)",
|
name: "CPU限制(%)",
|
||||||
type: "line",
|
type: "line",
|
||||||
yAxisIndex: 0,
|
yAxisIndex: 0,
|
||||||
data: new Array(props.data.usage.time!.length).fill(
|
data: new Array(props.data.usage.time?.length ?? 0).fill(
|
||||||
props.data.cpuLimit
|
props.data.cpuLimit
|
||||||
),
|
),
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
@@ -110,7 +115,7 @@ const initEChart = () => {
|
|||||||
name: "内存限制(MB)",
|
name: "内存限制(MB)",
|
||||||
type: "line",
|
type: "line",
|
||||||
yAxisIndex: 1,
|
yAxisIndex: 1,
|
||||||
data: new Array(props.data.usage.time!.length).fill(
|
data: new Array(props.data.usage.time?.length ?? 0).fill(
|
||||||
props.data.memoryLimit
|
props.data.memoryLimit
|
||||||
),
|
),
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
@@ -132,7 +137,6 @@ const terminalComponent = ref<WsHandle | null>(null);
|
|||||||
|
|
||||||
type ConfigHandle = {
|
type ConfigHandle = {
|
||||||
openConfigDialog: () => void;
|
openConfigDialog: () => void;
|
||||||
test: () => void;
|
|
||||||
};
|
};
|
||||||
const processConfigComponent = ref<ConfigHandle | null>(null);
|
const processConfigComponent = ref<ConfigHandle | null>(null);
|
||||||
|
|
||||||
@@ -182,8 +186,23 @@ onMounted(() => {
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
data: ProcessItem;
|
data: ProcessItem;
|
||||||
index: Number;
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const control = () => {
|
||||||
|
getContorl(props.data.uuid).then((e) => {
|
||||||
|
if (e.code === 0) {
|
||||||
|
snackbarStore.showSuccessMessage("sucess");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const del = () => {
|
||||||
|
deleteProcessConfig(props.data.uuid).then((e) => {
|
||||||
|
if (e.code === 0) {
|
||||||
|
snackbarStore.showSuccessMessage("sucess");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
@@ -231,8 +250,8 @@ const props = defineProps<{
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<v-list nav dense>
|
<v-list nav dense>
|
||||||
<v-list-item @click=""> 获取控制权 </v-list-item>
|
<v-list-item @click="control"> 获取控制权 </v-list-item>
|
||||||
<v-list-item @click=""> 删除进程 </v-list-item>
|
<v-list-item @click="del"> 删除进程 </v-list-item>
|
||||||
<v-list-item @click=""> 创建分享链接 </v-list-item>
|
<v-list-item @click=""> 创建分享链接 </v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
@@ -240,7 +259,7 @@ const props = defineProps<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 中间:ECharts -->
|
<!-- 中间:ECharts -->
|
||||||
<div :id="'echarts' + props.index" class="chart"></div>
|
<div :id="'echarts' + props.data.uuid" class="chart"></div>
|
||||||
|
|
||||||
<!-- 底部:按钮组 + 时间 -->
|
<!-- 底部:按钮组 + 时间 -->
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
@@ -274,7 +293,10 @@ const props = defineProps<{
|
|||||||
ref="terminalComponent"
|
ref="terminalComponent"
|
||||||
></TerminalPty>
|
></TerminalPty>
|
||||||
<TerminalPty v-else :data="props.data"></TerminalPty>
|
<TerminalPty v-else :data="props.data"></TerminalPty>
|
||||||
<ProcessConfig :data="props.data" ref="processConfigComponent"></ProcessConfig>
|
<ProcessConfig
|
||||||
|
:data="props.data"
|
||||||
|
ref="processConfigComponent"
|
||||||
|
></ProcessConfig>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@@ -105,9 +105,6 @@ const initPushItem = () => {
|
|||||||
density="compact"
|
density="compact"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="12" md="6">
|
<v-col cols="12" md="6">
|
||||||
<v-select
|
<v-select
|
||||||
label="终端类型"
|
label="终端类型"
|
||||||
@@ -118,7 +115,9 @@ const initPushItem = () => {
|
|||||||
density="compact"
|
density="compact"
|
||||||
></v-select>
|
></v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="6">
|
</v-row>
|
||||||
|
|
||||||
|
<v-divider class="my-4"></v-divider>
|
||||||
<v-select
|
<v-select
|
||||||
v-model="pushSelectedValues"
|
v-model="pushSelectedValues"
|
||||||
@change="updateJsonString"
|
@change="updateJsonString"
|
||||||
@@ -131,9 +130,6 @@ const initPushItem = () => {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
density="compact"
|
density="compact"
|
||||||
></v-select>
|
></v-select>
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
|
|
||||||
<v-divider class="my-4"></v-divider>
|
<v-divider class="my-4"></v-divider>
|
||||||
|
|
||||||
<v-row align="center">
|
<v-row align="center">
|
||||||
|
197
resources/src/components/process/ProcessCreate.vue
Normal file
197
resources/src/components/process/ProcessCreate.vue
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { postProcessConfig } from "~/src/api/process";
|
||||||
|
import { getPushList } from "~/src/api/push";
|
||||||
|
import { useSnackbarStore } from "~/src/stores/snackbarStore";
|
||||||
|
import { ProcessConfig } from "~/src/types/process/process";
|
||||||
|
|
||||||
|
const snackbarStore = useSnackbarStore();
|
||||||
|
const dialog = ref(false);
|
||||||
|
const configForm = ref<Partial<ProcessConfig>>({});
|
||||||
|
const pushItems = ref<{ value: any; label: string }[]>([]);
|
||||||
|
const pushSelectedValues = ref([]);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
pushSelectedValues,
|
||||||
|
(newValues) => {
|
||||||
|
configForm.value.pushIds = JSON.stringify(newValues);
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
defineExpose({
|
||||||
|
createProcessDialog: () => {
|
||||||
|
initPushItem();
|
||||||
|
dialog.value = true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateJsonString = () => {
|
||||||
|
configForm.value.pushIds = JSON.stringify(pushSelectedValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initPushItem = () => {
|
||||||
|
getPushList().then((resp) => {
|
||||||
|
// 3. 更新 ref 的 .value
|
||||||
|
if (resp.data) {
|
||||||
|
pushItems.value = resp.data.map((e) => ({
|
||||||
|
value: e.id,
|
||||||
|
label: `${e.remark} [${e.id}]`,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const create = () => {
|
||||||
|
postProcessConfig(configForm.value).then((e) => {
|
||||||
|
if (e.code === 0) {
|
||||||
|
snackbarStore.showSuccessMessage("sucess");
|
||||||
|
dialog.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<v-dialog v-model="dialog" width="700">
|
||||||
|
<v-card>
|
||||||
|
<v-card-title class="text-h5 grey lighten-2">
|
||||||
|
<v-icon left>mdi-cog</v-icon>
|
||||||
|
添加进程
|
||||||
|
</v-card-title>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-text-field
|
||||||
|
label="进程名称"
|
||||||
|
v-model="configForm.name"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="6">
|
||||||
|
<v-select
|
||||||
|
label="终端类型"
|
||||||
|
v-model="configForm.termType"
|
||||||
|
:items="['pty', 'std']"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
></v-select>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" md="12">
|
||||||
|
<v-text-field
|
||||||
|
label="工作目录"
|
||||||
|
v-model="configForm.cwd"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
|
||||||
|
<v-col cols="12" md="12">
|
||||||
|
<v-textarea
|
||||||
|
label="启动命令"
|
||||||
|
rows="2"
|
||||||
|
v-model="configForm.cmd"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
></v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-divider class="my-4"></v-divider>
|
||||||
|
<v-select
|
||||||
|
v-model="pushSelectedValues"
|
||||||
|
@change="updateJsonString"
|
||||||
|
:items="pushItems"
|
||||||
|
item-title="label"
|
||||||
|
item-value="value"
|
||||||
|
chips
|
||||||
|
label="状态推送"
|
||||||
|
multiple
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
></v-select>
|
||||||
|
<v-divider class="my-4"></v-divider>
|
||||||
|
|
||||||
|
<v-row align="center">
|
||||||
|
<v-col cols="12" sm="3">
|
||||||
|
<v-switch
|
||||||
|
v-model="configForm.cgroupEnable"
|
||||||
|
label="资源限制"
|
||||||
|
color="primary"
|
||||||
|
hide-details
|
||||||
|
></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="4">
|
||||||
|
<v-text-field
|
||||||
|
:disabled="!configForm.cgroupEnable"
|
||||||
|
label="CPU 限制 (%)"
|
||||||
|
type="number"
|
||||||
|
v-model.number="configForm.cpuLimit"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
hide-details="auto"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="4">
|
||||||
|
<v-text-field
|
||||||
|
:disabled="!configForm.cgroupEnable"
|
||||||
|
label="内存限制 (MB)"
|
||||||
|
type="number"
|
||||||
|
v-model.number="configForm.memoryLimit"
|
||||||
|
variant="outlined"
|
||||||
|
density="compact"
|
||||||
|
hide-details="auto"
|
||||||
|
></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
<v-divider class="my-4"></v-divider>
|
||||||
|
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" sm="4">
|
||||||
|
<v-switch
|
||||||
|
v-model="configForm.autoRestart"
|
||||||
|
label="自动重启"
|
||||||
|
color="primary"
|
||||||
|
hide-details
|
||||||
|
></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="4">
|
||||||
|
<v-switch
|
||||||
|
:disabled="!configForm.autoRestart"
|
||||||
|
v-model="configForm.compulsoryRestart"
|
||||||
|
label="强制重启"
|
||||||
|
color="primary"
|
||||||
|
hide-details
|
||||||
|
></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm="4">
|
||||||
|
<v-switch
|
||||||
|
v-model="configForm.logReport"
|
||||||
|
label="日志上报"
|
||||||
|
color="primary"
|
||||||
|
hide-details
|
||||||
|
></v-switch>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
|
||||||
|
<v-divider></v-divider>
|
||||||
|
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn variant="text" color="grey-darken-1" @click="dialog = false">
|
||||||
|
<v-icon left>mdi-close</v-icon>
|
||||||
|
取消
|
||||||
|
</v-btn>
|
||||||
|
<v-btn variant="flat" color="primary" @click="create">
|
||||||
|
<v-icon left>mdi-check</v-icon>
|
||||||
|
确认
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
@@ -5,8 +5,8 @@ import { ProcessItem } from "~/src/types/process/process";
|
|||||||
import { Terminal } from "xterm";
|
import { Terminal } from "xterm";
|
||||||
import { FitAddon } from "xterm-addon-fit";
|
import { FitAddon } from "xterm-addon-fit";
|
||||||
import { AttachAddon } from "xterm-addon-attach";
|
import { AttachAddon } from "xterm-addon-attach";
|
||||||
import { CanvasAddon } from '@xterm/addon-canvas';
|
import { CanvasAddon } from "@xterm/addon-canvas";
|
||||||
import 'xterm/css/xterm.css';
|
import "xterm/css/xterm.css";
|
||||||
|
|
||||||
const snackbarStore = useSnackbarStore();
|
const snackbarStore = useSnackbarStore();
|
||||||
const dialog = ref(false);
|
const dialog = ref(false);
|
||||||
@@ -35,7 +35,6 @@ watch(dialog, (newValue) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const initWebSocketPty = () => {
|
const initWebSocketPty = () => {
|
||||||
if (!xtermEl.value) {
|
if (!xtermEl.value) {
|
||||||
snackbarStore.showErrorMessage("终端容器初始化失败");
|
snackbarStore.showErrorMessage("终端容器初始化失败");
|
||||||
@@ -46,7 +45,9 @@ const initWebSocketPty = () => {
|
|||||||
const initialRows = Math.floor(xtermEl.value.clientHeight / 19);
|
const initialRows = Math.floor(xtermEl.value.clientHeight / 19);
|
||||||
|
|
||||||
const baseUrl = `ws://${window.location.hostname}:8797/api/ws`;
|
const baseUrl = `ws://${window.location.hostname}:8797/api/ws`;
|
||||||
const url = `${baseUrl}?uuid=${props.data.uuid}&token=${localStorage.getItem("token")}&cols=${initialCols}&rows=${initialRows}`;
|
const url = `${baseUrl}?uuid=${props.data.uuid}&token=${localStorage.getItem(
|
||||||
|
"token"
|
||||||
|
)}&cols=${initialCols}&rows=${initialRows}`;
|
||||||
|
|
||||||
initSocket(url);
|
initSocket(url);
|
||||||
};
|
};
|
||||||
@@ -61,6 +62,7 @@ const initSocket = (url: string) => {
|
|||||||
|
|
||||||
socket.onclose = () => {
|
socket.onclose = () => {
|
||||||
snackbarStore.showErrorMessage("终端连接断开");
|
snackbarStore.showErrorMessage("终端连接断开");
|
||||||
|
dialog.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.onerror = (err) => {
|
socket.onerror = (err) => {
|
||||||
@@ -71,15 +73,15 @@ const initSocket = (url: string) => {
|
|||||||
|
|
||||||
const initTerm = () => {
|
const initTerm = () => {
|
||||||
if (!socket || !xtermEl.value) return;
|
if (!socket || !xtermEl.value) return;
|
||||||
|
const showCursor = props.data.state.state === 3;
|
||||||
term = new Terminal({
|
term = new Terminal({
|
||||||
// rendererType: "canvas", // 已通过插件方式加载,此处无需设置
|
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
disableStdin: false,
|
disableStdin: false,
|
||||||
cursorBlink: true,
|
cursorBlink: showCursor,
|
||||||
|
cursorStyle: "block",
|
||||||
theme: {
|
theme: {
|
||||||
foreground: "#ECECEC",
|
foreground: "#ECECEC",
|
||||||
cursor: "help",
|
cursor: "help"
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,6 +108,13 @@ const wsClose = () => {
|
|||||||
cleanup();
|
cleanup();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toolbarColor = computed(() => {
|
||||||
|
if (props.data.state.state == 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return "red";
|
||||||
|
});
|
||||||
|
|
||||||
const cleanup = () => {
|
const cleanup = () => {
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener("resize", handleResize);
|
||||||
if (term) {
|
if (term) {
|
||||||
@@ -129,7 +138,7 @@ onUnmounted(() => {
|
|||||||
hide-overlay
|
hide-overlay
|
||||||
transition="dialog-bottom-transition"
|
transition="dialog-bottom-transition"
|
||||||
v-model="dialog"
|
v-model="dialog"
|
||||||
@update:modelValue="val => !val && cleanup()"
|
@update:modelValue="(val) => !val && cleanup()"
|
||||||
>
|
>
|
||||||
<v-card
|
<v-card
|
||||||
style="
|
style="
|
||||||
@@ -141,8 +150,8 @@ onUnmounted(() => {
|
|||||||
>
|
>
|
||||||
<v-toolbar
|
<v-toolbar
|
||||||
dense
|
dense
|
||||||
|
:color="toolbarColor"
|
||||||
dark
|
dark
|
||||||
color="blue-grey darken-4"
|
|
||||||
style="height: 35px; flex-grow: 0"
|
style="height: 35px; flex-grow: 0"
|
||||||
>
|
>
|
||||||
<v-toolbar-title style="height: 100%"
|
<v-toolbar-title style="height: 100%"
|
||||||
@@ -155,7 +164,11 @@ onUnmounted(() => {
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</v-toolbar-items>
|
</v-toolbar-items>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<div id="xterm" ref="xtermEl" style="flex-grow: 1; height: 100%; width: 100%;"></div>
|
<div
|
||||||
|
id="xterm"
|
||||||
|
ref="xtermEl"
|
||||||
|
style="flex-grow: 1; height: 100%; width: 100%"
|
||||||
|
></div>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
@@ -1,22 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="toolbar">
|
||||||
|
<ConfirmButton @confirm="startAll" color="#3CB371">全部启动</ConfirmButton>
|
||||||
|
<ConfirmButton @confirm="killAll" color="#CD5555">全部停止</ConfirmButton>
|
||||||
|
<v-btn
|
||||||
|
size="small"
|
||||||
|
variant="tonal"
|
||||||
|
color="blue"
|
||||||
|
@click="processCreateComponent?.createProcessDialog()"
|
||||||
|
>创建<v-icon dark right> mdi-plus-circle </v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
<v-container>
|
<v-container>
|
||||||
|
<!-- 顶部工具栏 -->
|
||||||
|
|
||||||
|
<!-- 主体网格 -->
|
||||||
<div class="flex-grid">
|
<div class="flex-grid">
|
||||||
<div v-for="(i, v) in processData" class="responsive-box">
|
<div v-for="(i, v) in processData" :key="i.uuid" class="responsive-box">
|
||||||
<ProcessCard :data="i" :index="v"></ProcessCard>
|
<ProcessCard :data="i" :index="v" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
<ProcessCreate ref="processCreateComponent"></ProcessCreate>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ProcessCard from "@/components/process/ProcessCard.vue";
|
import ProcessCard from "@/components/process/ProcessCard.vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { getProcessList } from "~/src/api/process";
|
import {
|
||||||
|
getProcessList,
|
||||||
|
killProcessAll,
|
||||||
|
startProcessAll,
|
||||||
|
} from "~/src/api/process";
|
||||||
|
import ConfirmButton from "~/src/components/ConfirmButton.vue";
|
||||||
|
import ProcessCreate from "~/src/components/process/ProcessCreate.vue";
|
||||||
|
import { useSnackbarStore } from "~/src/stores/snackbarStore";
|
||||||
import { ProcessItem } from "~/src/types/process/process";
|
import { ProcessItem } from "~/src/types/process/process";
|
||||||
|
type CreateHandle = {
|
||||||
|
createProcessDialog: () => void;
|
||||||
|
test: () => void;
|
||||||
|
};
|
||||||
|
const processCreateComponent = ref<CreateHandle | null>(null);
|
||||||
const processData = ref<ProcessItem[]>();
|
const processData = ref<ProcessItem[]>();
|
||||||
const uuid: string = crypto.randomUUID();
|
const uuid: string = crypto.randomUUID();
|
||||||
|
const snackbarStore = useSnackbarStore();
|
||||||
const initProcessData = () => {
|
const initProcessData = () => {
|
||||||
getProcessList().then((e) => {
|
getProcessList().then((e) => {
|
||||||
processData.value = e.data!.sort((a, b) => a.name.localeCompare(b.name));
|
processData.value = e.data!.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
@@ -24,7 +50,23 @@ const initProcessData = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var cancelTokenSource;
|
const startAll = () => {
|
||||||
|
startProcessAll().then((e) => {
|
||||||
|
if (e.code === 0) {
|
||||||
|
snackbarStore.showSuccessMessage("sucess");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const killAll = () => {
|
||||||
|
killProcessAll().then((e) => {
|
||||||
|
if (e.code === 0) {
|
||||||
|
snackbarStore.showSuccessMessage("sucess");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let cancelTokenSource: any;
|
||||||
const getProcessListWait = () => {
|
const getProcessListWait = () => {
|
||||||
cancelTokenSource = axios.CancelToken.source();
|
cancelTokenSource = axios.CancelToken.source();
|
||||||
axios
|
axios
|
||||||
@@ -51,26 +93,36 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 工具栏样式 */
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end; /* 靠右对齐 */
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 4px 0;
|
||||||
|
border-bottom: 1px solid #eee; /* 轻量分隔线 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 原来的网格样式 */
|
||||||
.flex-grid {
|
.flex-grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap; /* 自动换行 */
|
flex-wrap: wrap;
|
||||||
justify-content: space-between; /* 两边与中间间距均匀 */
|
justify-content: space-between;
|
||||||
gap: 80px; /* 每个 div 之间的间距 */
|
gap: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsive-box {
|
.responsive-box {
|
||||||
flex: 1 1 300px; /* 最小宽度 300px */
|
flex: 1 1 300px;
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
background: #ffffff; /* 改为白色背景 */
|
background: #ffffff;
|
||||||
border-radius: 16px; /* 圆角 */
|
border-radius: 16px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); /* 柔和阴影 */
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 10px; /* 内边距,让内容不贴边 */
|
padding: 10px;
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease; /* 交互动画 */
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 悬停效果 */
|
|
||||||
.responsive-box:hover {
|
.responsive-box:hover {
|
||||||
transform: translateY(-6px);
|
transform: translateY(-6px);
|
||||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
Reference in New Issue
Block a user