This commit is contained in:
bwcx_jzy 2022-01-23 14:40:06 +08:00
parent db41a8cb95
commit 4690ed876f
No known key found for this signature in database
GPG Key ID: 5E48E9372088B9E5
10 changed files with 283 additions and 182 deletions

View File

@ -14,9 +14,9 @@
8. h2 数据库升级 2.0
9. 容器构建
10. docker ui
11. 节点大屏
11. ~~节点大屏~~
12. ~~实时阅读日志文件~~
13. 配置分发
13. ~~配置分发~~
# 2.7.x

View File

@ -1,18 +1,13 @@
package io.jpom.controller.node;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.Page;
import cn.hutool.db.sql.Direction;
import cn.hutool.db.sql.Order;
import cn.hutool.extra.servlet.ServletUtil;
import cn.jiangzeyin.common.JsonMessage;
import com.alibaba.fastjson.JSONObject;
import io.jpom.common.BaseServerController;
import io.jpom.common.forward.NodeForward;
import io.jpom.common.forward.NodeUrl;
@ -22,13 +17,11 @@ import io.jpom.permission.SystemPermission;
import io.jpom.service.dblog.DbSystemMonitorLogService;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
@ -46,10 +39,11 @@ public class NodeWelcomeController extends BaseServerController {
this.dbSystemMonitorLogService = dbSystemMonitorLogService;
}
@PostMapping(value = "nodeMonitor_data.json", produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = "node_monitor_data.json", produces = MediaType.APPLICATION_JSON_VALUE)
public String nodeMonitorJson() {
JSONObject object = getData();
return JsonMessage.getString(200, "ok", object);
List<SystemMonitorLog> list = this.getList();
Assert.notEmpty(list, "没有查询到任何数据");
return JsonMessage.getString(200, "ok", list);
}
private List<SystemMonitorLog> getList() {
@ -59,7 +53,7 @@ public class NodeWelcomeController extends BaseServerController {
if (StrUtil.hasEmpty(startDateStr, endDateStr)) {
SystemMonitorLog systemMonitorLog = new SystemMonitorLog();
systemMonitorLog.setNodeId(node.getId());
return dbSystemMonitorLogService.queryList(systemMonitorLog, 100, new Order("monitorTime", Direction.DESC));
return dbSystemMonitorLogService.queryList(systemMonitorLog, 500, new Order("monitorTime", Direction.DESC));
}
// 处理时间
DateTime startDate = DateUtil.parse(startDateStr);
@ -72,7 +66,7 @@ public class NodeWelcomeController extends BaseServerController {
long endTime = endDate.getTime();
// 开启了节点信息采集
Page pageObj = new Page(1, 2000);
Page pageObj = new Page(1, 5000);
pageObj.addOrder(new Order("monitorTime", Direction.DESC));
Entity entity = Entity.create();
entity.set("nodeId", node.getId());
@ -81,57 +75,57 @@ public class NodeWelcomeController extends BaseServerController {
return dbSystemMonitorLogService.listPageOnlyResult(entity, pageObj);
}
private JSONObject getData() {
List<SystemMonitorLog> list = getList();
Assert.notEmpty(list, "没有查询到任何数据");
List<JSONObject> series = new ArrayList<>();
List<String> scale = new ArrayList<>();
for (int i = list.size() - 1; i >= 0; i--) {
SystemMonitorLog systemMonitorLog = list.get(i);
scale.add(new DateTime(systemMonitorLog.getMonitorTime()).toString(DatePattern.NORM_DATETIME_PATTERN));
JSONObject jsonObject = new JSONObject();
jsonObject.put("cpu", systemMonitorLog.getOccupyCpu());
jsonObject.put("memory", systemMonitorLog.getOccupyMemory());
jsonObject.put("memoryUsed", systemMonitorLog.getOccupyMemoryUsed());
jsonObject.put("disk", systemMonitorLog.getOccupyDisk());
series.add(jsonObject);
}
// private JSONObject getData() {
// List<SystemMonitorLog> list = getList();
// Assert.notEmpty(list, "没有查询到任何数据");
// List<JSONObject> series = new ArrayList<>();
// List<String> scale = new ArrayList<>();
// for (int i = list.size() - 1; i >= 0; i--) {
// SystemMonitorLog systemMonitorLog = list.get(i);
// scale.add(new DateTime(systemMonitorLog.getMonitorTime()).toString(DatePattern.NORM_DATETIME_PATTERN));
// JSONObject jsonObject = new JSONObject();
// jsonObject.put("cpu", systemMonitorLog.getOccupyCpu());
// jsonObject.put("memory", systemMonitorLog.getOccupyMemory());
// jsonObject.put("memoryUsed", systemMonitorLog.getOccupyMemoryUsed());
// jsonObject.put("disk", systemMonitorLog.getOccupyDisk());
// series.add(jsonObject);
// }
//
// JSONObject object = new JSONObject();
// object.put("scales", scale);
// object.put("series", series);
// return object;
// }
//
// @PostMapping(value = "getTop", produces = MediaType.APPLICATION_JSON_VALUE)
// public String getTop() {
// JSONObject object = getData();
// return JsonMessage.getString(200, "ok", object);
// }
JSONObject object = new JSONObject();
object.put("scales", scale);
object.put("series", series);
return object;
}
@PostMapping(value = "getTop", produces = MediaType.APPLICATION_JSON_VALUE)
public String getTop() {
JSONObject object = getData();
return JsonMessage.getString(200, "ok", object);
}
@RequestMapping(value = "exportTop")
public void exportTop(String time) throws UnsupportedEncodingException {
List<SystemMonitorLog> result = getList();
if (CollUtil.isEmpty(result)) {
// NodeForward.requestDownload(node, getRequest(), getResponse(), NodeUrl.exportTop);
} else {
NodeModel node = getNode();
StringBuilder buf = new StringBuilder();
buf.append("监控时间").append(",占用cpu").append(",占用内存").append(",占用磁盘").append("\r\n");
for (SystemMonitorLog log : result) {
long monitorTime = log.getMonitorTime();
buf.append(DateUtil.date(monitorTime)).append(StrUtil.COMMA)
.append(log.getOccupyCpu()).append("%").append(StrUtil.COMMA)
.append(log.getOccupyMemory()).append("%").append(StrUtil.COMMA)
.append(log.getOccupyDisk()).append("%").append("\r\n");
}
String fileName = URLEncoder.encode("Jpom系统监控-" + node.getId(), "UTF-8");
HttpServletResponse response = getResponse();
response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(StandardCharsets.UTF_8), "GBK") + ".csv");
response.setContentType("text/csv;charset=utf-8");
ServletUtil.write(getResponse(), buf.toString(), CharsetUtil.UTF_8);
}
}
// @RequestMapping(value = "exportTop")
// public void exportTop(String time) throws UnsupportedEncodingException {
// List<SystemMonitorLog> result = getList();
// if (CollUtil.isEmpty(result)) {
// // NodeForward.requestDownload(node, getRequest(), getResponse(), NodeUrl.exportTop);
// } else {
// NodeModel node = getNode();
// StringBuilder buf = new StringBuilder();
// buf.append("监控时间").append(",占用cpu").append(",占用内存").append(",占用磁盘").append("\r\n");
// for (SystemMonitorLog log : result) {
// long monitorTime = log.getMonitorTime();
// buf.append(DateUtil.date(monitorTime)).append(StrUtil.COMMA)
// .append(log.getOccupyCpu()).append("%").append(StrUtil.COMMA)
// .append(log.getOccupyMemory()).append("%").append(StrUtil.COMMA)
// .append(log.getOccupyDisk()).append("%").append("\r\n");
// }
// String fileName = URLEncoder.encode("Jpom系统监控-" + node.getId(), "UTF-8");
// HttpServletResponse response = getResponse();
// response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes(StandardCharsets.UTF_8), "GBK") + ".csv");
// response.setContentType("text/csv;charset=utf-8");
// ServletUtil.write(getResponse(), buf.toString(), CharsetUtil.UTF_8);
// }
// }
@RequestMapping(value = "processList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public String getProcessList() {

View File

@ -40,6 +40,7 @@ public class SystemMonitorLog extends BaseDbModel {
private String nodeId;
/**
* 监控时间
* 插件端返回的时间
*/
private Long monitorTime;
/**
@ -58,6 +59,18 @@ public class SystemMonitorLog extends BaseDbModel {
* 占用磁盘
*/
private Double occupyDisk;
/**
* 网络时间
*/
private Integer networkTime;
public Integer getNetworkTime() {
return networkTime;
}
public void setNetworkTime(Integer networkTime) {
this.networkTime = networkTime;
}
public String getNodeId() {
return nodeId;

View File

@ -130,16 +130,17 @@ public class NodeMonitor {
JSONObject jsonObject;
if (jsonMessage.getCode() == 200) {
jsonObject = jsonMessage.getData(JSONObject.class);
jsonObject.put("networkTime", networkTime);
this.save(modelList, nodeTopInfo, jsonObject);
} else {
// 状态码错
jsonObject = new JSONObject();
jsonObject.put("status", 3);
jsonObject.put("failureMsg", jsonMessage.toString());
jsonObject.put("networkTime", networkTime);
this.save(modelList, nodeTopInfo, jsonObject);
}
jsonObject.put("networkTime", networkTime);
if (nodeTopInfo != null) {
nodeTopInfo.put("networkTime", networkTime);
}
this.save(modelList, nodeTopInfo, jsonObject);
} catch (AuthorizeException agentException) {
this.save(modelList, 2, agentException.getMessage());
} catch (Exception e) {
@ -161,6 +162,7 @@ public class NodeMonitor {
log.setOccupyDisk(systemMonitor.getDouble("disk"));
log.setOccupyCpu(systemMonitor.getDouble("cpu"));
log.setMonitorTime(systemMonitor.getLongValue("time"));
log.setNetworkTime(systemMonitor.getIntValue("networkTime"));
log.setNodeId(nodeModel.getId());
return log;
}).collect(Collectors.toList());

View File

@ -21,3 +21,6 @@ ALTER TABLE BUILDHISTORYLOG
ALTER TABLE SSHTERMINALEXECUTELOG
ALTER COLUMN commands CLOB comment '操作的命令';
ALTER TABLE SYSTEMMONITORLOG
ADD IF NOT EXISTS networkTime int COMMENT '延迟时间ms';

View File

@ -1,4 +1,6 @@
import axios from "./config";
import { parseTime } from "@/utils/time";
import echarts from "echarts";
// node 列表
export function getStatist(params) {
@ -23,8 +25,48 @@ export function statusStat() {
});
}
export function generateChart(data) {
let cpuItem = {
const defaultData = {
title: {
// text: "系统 Top 监控",
},
tooltip: {
trigger: "axis",
},
legend: {
// data: legends,
},
color: ["#5470c6", "#91cc75", "#fac858", "#ee6666", "#73c0de", "#3ba272", "#fc8452", "#9a60b4", "#ea7ccc"],
grid: {
left: "1%",
right: "2%",
bottom: "1%",
containLabel: true,
},
xAxis: {
type: "category",
boundaryGap: false,
// data: scales,
},
calculable: true,
// yAxis: {
// type: "value",
// axisLabel: {
// // 设置y轴数值为%
// formatter: "{value} %",
// },
// max: 100,
// },
dataZoom: [{ type: "inside" }, { type: "slider" }],
// series: series,
};
/**
* 节点系统统计
* @param { JSON } data
* @returns
*/
export function generateNodeTopChart(data) {
const cpuItem = {
name: "cpu占用",
type: "line",
data: [],
@ -32,65 +74,55 @@ export function generateChart(data) {
// 设置折线为曲线
smooth: true,
};
let diskItem = {
const diskItem = {
name: "磁盘占用",
type: "line",
data: [],
showSymbol: false,
smooth: true,
};
let memoryItem = {
const memoryItem = {
name: "内存占用(累计)",
type: "line",
data: [],
showSymbol: false,
smooth: true,
};
let memoryUsedItem = {
const memoryUsedItem = {
name: "内存占用",
type: "line",
data: [],
showSymbol: false,
smooth: true,
};
data.series.forEach((item) => {
cpuItem.data.push(parseFloat(item.cpu));
diskItem.data.push(parseFloat(item.disk));
memoryItem.data.push(parseFloat(item.memory));
if (item.memoryUsed) {
memoryUsedItem.data.push(parseFloat(item.memoryUsed));
const scales = [];
for (var i = data.length - 1; i >= 0; i--) {
const item = data[i];
cpuItem.data.push(parseFloat(item.occupyCpu));
diskItem.data.push(parseFloat(item.occupyDisk));
memoryItem.data.push(parseFloat(item.occupyMemory));
if (item.occupyMemoryUsed) {
memoryUsedItem.data.push(parseFloat(item.occupyMemoryUsed));
}
});
let series = [cpuItem, memoryItem, diskItem];
scales.push(parseTime(item.monitorTime));
}
const series = [cpuItem, memoryItem, diskItem];
if (memoryUsedItem.data.length > 0) {
series.push(memoryUsedItem);
}
let legends = series.map((data) => {
const legends = series.map((data) => {
return data.name;
});
// 指定图表的配置项和数据
return {
title: {
text: "系统 Top 监控",
},
tooltip: {
trigger: "axis",
},
return Object.assign({}, defaultData, {
legend: {
data: legends,
},
grid: {
left: "1%",
right: "2%",
bottom: "1%",
containLabel: true,
},
xAxis: {
type: "category",
boundaryGap: false,
data: data.scales,
data: scales,
},
calculable: true,
yAxis: {
type: "value",
axisLabel: {
@ -99,9 +131,70 @@ export function generateChart(data) {
},
max: 100,
},
dataZoom: [{ type: "inside" }, { type: "slider" }],
series: series,
});
}
/**
* 节点网络延迟
* @param { JSON } data
* @returns
*/
export function generateNodeNetworkTimeChart(data) {
const dataArray = {
name: "网络延迟ms",
type: "line",
data: [],
showSymbol: false,
// 设置折线为曲线
smooth: true,
};
const scales = [];
for (var i = data.length - 1; i >= 0; i--) {
const item = data[i];
dataArray.data.push(parseFloat(item.networkTime));
scales.push(parseTime(item.monitorTime));
}
const series = [dataArray];
const legends = series.map((data) => {
return data.name;
});
// 指定图表的配置项和数据
return Object.assign({}, defaultData, {
legend: {
data: legends,
},
xAxis: {
data: scales,
},
yAxis: {
type: "value",
axisLabel: {
formatter: "{value} ms",
},
},
series: series,
});
}
/**
*
* @param {*} data
* @param {String} domId
* @returns
*/
export function drawChart(data, domId, parseFn) {
const historyChartDom = document.getElementById(domId, domId);
if (!historyChartDom) {
return;
}
const option = parseFn(data);
// 绘制图表
const historyChart = echarts.init(historyChartDom);
historyChart.setOption(option);
}
export const status = {
@ -110,11 +203,3 @@ export const status = {
2: "授权信息错误",
3: "状态码错误",
};
// export const nodeMonitorCycle = {
// "-30": "30 秒",
// 1: "1 分钟",
// 5: "5 分钟",
// 10: "10 分钟",
// 30: "30 分钟",
// };

View File

@ -144,17 +144,17 @@ export function deleteNode(id) {
});
}
// 节点 top 命令
export function getNodeTop(nodeId) {
return axios({
url: "/node/getTop",
method: "post",
data: { nodeId },
headers: {
loading: "no",
},
});
}
// // 节点 top 命令
// export function getNodeTop(nodeId) {
// return axios({
// url: "/node/getTop",
// method: "post",
// data: { nodeId },
// headers: {
// loading: "no",
// },
// });
// }
// 获取进程列表
export function getProcessList(data) {
@ -189,7 +189,7 @@ export function killPid(params) {
*/
export function nodeMonitorData(params) {
return axios({
url: "/node/nodeMonitor_data.json",
url: "/node/node_monitor_data.json",
method: "post",
data: params,
});

View File

@ -2,7 +2,19 @@
<div>
<div ref="filter" class="filter">
<a-space>
<a-range-picker class="filter-item" v-model="timeRange" :show-time="{ format: 'HH:mm:ss' }" format="YYYY-MM-DD HH:mm:ss" valueFormat="YYYY-MM-DD HH:mm:ss" />
<a-range-picker
:ranges="{ 今天: [moment().startOf('day'), moment()], 昨天: [moment().add(-1, 'days').startOf('day'), moment().add(-1, 'days').endOf('day')] }"
:disabled-date="
(current) => {
return current && current >= moment().endOf('day');
}
"
class="filter-item"
v-model="timeRange"
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
valueFormat="YYYY-MM-DD HH:mm:ss"
/>
<a-button type="primary" @click="handleFilter">搜索</a-button>
</a-space>
</div>
@ -11,15 +23,17 @@
</template>
<script>
import { nodeMonitorData } from "@/api/node";
import echarts from "echarts";
import { generateChart } from "@/api/node-stat";
import { drawChart, generateNodeTopChart, generateNodeNetworkTimeChart } from "@/api/node-stat";
import moment from "moment";
export default {
components: {},
props: {
nodeId: {
type: String,
},
type: {
type: String,
},
},
data() {
return {
@ -33,6 +47,7 @@ export default {
destroyed() {},
watch: {},
methods: {
moment,
//
handleFilter() {
const params = {
@ -42,23 +57,14 @@ export default {
//
nodeMonitorData(params).then((res) => {
if (res.code === 200) {
this.drawHistoryChart(res.data);
if (this.type === "networkTime") {
drawChart(res.data, "historyChart", generateNodeNetworkTimeChart);
} else {
drawChart(res.data, "historyChart", generateNodeTopChart);
}
}
});
},
//
drawHistoryChart(historyData) {
const historyChartDom = document.getElementById("historyChart");
if (!historyChartDom) {
return;
}
const option = generateChart(historyData);
//
const historyChart = echarts.init(historyChartDom);
historyChart.setOption(option);
},
},
};
</script>

View File

@ -52,11 +52,11 @@
</div>
</template>
<script>
import { getNodeTop, getProcessList, killPid } from "@/api/node";
import echarts from "echarts";
import { nodeMonitorData, getProcessList, killPid } from "@/api/node";
import CustomSelect from "@/components/customSelect";
import NodeTop from "@/pages/node/node-layout/node-top";
import { generateChart } from "@/api/node-stat";
import { generateNodeTopChart, drawChart } from "@/api/node-stat";
export default {
components: {
@ -137,24 +137,12 @@ export default {
},
// top
loadNodeTop() {
getNodeTop(this.node.id).then((res) => {
nodeMonitorData({ nodeId: this.node.id }).then((res) => {
if (res.code === 200) {
this.drawTopChart(res.data);
drawChart(res.data, "top-chart", generateNodeTopChart);
}
});
},
// top
drawTopChart(topData) {
let topChartDom = document.getElementById("top-chart");
if (!topChartDom) {
return;
}
let option = generateChart(topData);
//
const topChart = echarts.init(topChartDom);
topChart.setOption(option);
},
//
loadNodeProcess(v) {
this.loading = true;

View File

@ -60,27 +60,37 @@
<span>{{ parseTime(text, "{m}-{d} {h}:{i}:{s}") }}</span>
</a-tooltip>
<template slot="progress" slot-scope="text, record">
<a-tooltip v-if="record.status === 0" placement="topLeft" :title="`${text}%`">
<a-progress
@click="handleHistory(record)"
:percent="text"
:stroke-color="{
from: '#87d068',
to: '#108ee9',
}"
size="small"
status="active"
:showInfo="false"
/>
<template slot="networkTime" slot-scope="text, record">
<a-tooltip @click="handleHistory(record, 'networkTime')" v-if="record.status === 0" placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
<span v-else>-</span>
</template>
<template slot="progress" slot-scope="text, record">
<div v-if="parseFloat(text) >= 0">
<a-tooltip v-if="record.status === 0" placement="topLeft" :title="`当前值:${text}% 点击查看历史值`">
<a-progress
@click="handleHistory(record, 'nodeTop')"
:percent="text"
:stroke-color="{
'0%': '#87d068',
'30%': '#87d068',
'100%': '#108ee9',
}"
size="small"
status="active"
/>
</a-tooltip>
<span v-else>-</span>
</div>
<div v-else>-</div>
</template>
</a-table>
</a-space>
<!-- 历史监控 -->
<a-modal v-model="monitorVisible" width="75%" :title="`${this.temp.name}历史监控图表`" :footer="null" :maskClosable="false">
<node-top v-if="monitorVisible" :nodeId="this.temp.id"></node-top>
<node-top v-if="monitorVisible" :type="this.temp.type" :nodeId="this.temp.id"></node-top>
</a-modal>
</div>
</template>
@ -113,11 +123,11 @@ export default {
columns: [
{ title: "节点名称", dataIndex: "name", sorter: true, key: "name", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{ title: "节点地址", dataIndex: "url", sorter: true, key: "url", ellipsis: true, scopedSlots: { customRender: "tooltip" } },
{ title: "cpu", dataIndex: "occupyCpu", sorter: true, key: "occupyCpu", ellipsis: true, scopedSlots: { customRender: "progress" } },
{ title: "disk", dataIndex: "occupyDisk", sorter: true, key: "occupyDisk", ellipsis: true, scopedSlots: { customRender: "progress" } },
{ title: "memory", dataIndex: "occupyMemory", sorter: true, key: "occupyMemory", ellipsis: true, scopedSlots: { customRender: "progress" } },
{ title: "memoryUsed", dataIndex: "occupyMemoryUsed", sorter: true, key: "occupyMemoryUsed", ellipsis: true, scopedSlots: { customRender: "progress" } },
{ title: "延迟(ms)", width: 100, dataIndex: "networkTime", defaultSortOrder: "descend", sorter: true, key: "networkTime", ellipsis: true, scopedSlots: { customRender: "tooltipStatus" } },
{ title: "cpu", dataIndex: "occupyCpu", sorter: true, key: "occupyCpu", scopedSlots: { customRender: "progress" } },
{ title: "disk", dataIndex: "occupyDisk", sorter: true, key: "occupyDisk", scopedSlots: { customRender: "progress" } },
{ title: "memory", dataIndex: "occupyMemory", sorter: true, key: "occupyMemory", scopedSlots: { customRender: "progress" } },
{ title: "memoryUsed", dataIndex: "occupyMemoryUsed", sorter: true, key: "occupyMemoryUsed", scopedSlots: { customRender: "progress" } },
{ title: "延迟(ms)", width: 100, dataIndex: "networkTime", defaultSortOrder: "descend", sorter: true, key: "networkTime", ellipsis: true, scopedSlots: { customRender: "networkTime" } },
{ title: "运行时间", dataIndex: "upTimeStr", sorter: true, key: "upTimeStr", ellipsis: true, scopedSlots: { customRender: "tooltipStatus" } },
{ title: "状态", width: 100, dataIndex: "status", sorter: true, key: "status", ellipsis: true, scopedSlots: { customRender: "status" } },
{
@ -157,7 +167,7 @@ export default {
methods: {
//
loadData(pointerEvent) {
this.list = [];
//this.list = [];
this.listQuery.page = pointerEvent?.altKey || pointerEvent?.ctrlKey ? 1 : this.listQuery.page;
this.loading = true;
getStatist(this.listQuery).then((res) => {
@ -195,12 +205,12 @@ export default {
onFinish() {
this.loadData();
},
parseTime: parseTime,
parseTime,
//
handleHistory(record) {
handleHistory(record, type) {
this.monitorVisible = true;
this.temp = record;
// { ...this.temp, record };
this.temp = { ...this.temp, type };
},
},
};