diff --git a/public/config.js b/public/config.js index 10a7a0a..3da77e3 100644 --- a/public/config.js +++ b/public/config.js @@ -11,7 +11,8 @@ window.$$nazhuaConfig = { // showLantern: true, // 是否显示灯笼 enableInnerSearch: true, // 启用内部搜索 // listServerItemTypeToggle: true, // 服务器列表项类型切换 - // listServerItemType: 'row', // 服务器列表项类型 card/row row列表模式移动端自动切换至card + listServerItemType: 'row', // 服务器列表项类型 card/row/status row列表模式移动端自动切换至card + // serverStatusColumnsTpl: null, // 服务器状态列配置模板 // listServerStatusType: 'progress', // 服务器状态类型--列表 // listServerRealTimeShowLoad: true, // 列表显示服务器实时负载 // detailServerStatusType: 'progress', // 服务器状态类型--详情页 diff --git a/src/views/components/server-list-by-server-status/main.vue b/src/views/components/server-list-by-server-status/main.vue new file mode 100644 index 0000000..f7f997e --- /dev/null +++ b/src/views/components/server-list-by-server-status/main.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/src/views/components/server-list-by-server-status/server-info/conns.vue b/src/views/components/server-list-by-server-status/server-info/conns.vue new file mode 100644 index 0000000..73cf630 --- /dev/null +++ b/src/views/components/server-list-by-server-status/server-info/conns.vue @@ -0,0 +1,68 @@ + + + + + diff --git a/src/views/components/server-list-by-server-status/server-info/country.vue b/src/views/components/server-list-by-server-status/server-info/country.vue new file mode 100644 index 0000000..e8b76b9 --- /dev/null +++ b/src/views/components/server-list-by-server-status/server-info/country.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/views/components/server-list-by-server-status/server-info/net-speed.vue b/src/views/components/server-list-by-server-status/server-info/net-speed.vue new file mode 100644 index 0000000..4fcb6b3 --- /dev/null +++ b/src/views/components/server-list-by-server-status/server-info/net-speed.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/views/components/server-list-by-server-status/server-info/status-icon.vue b/src/views/components/server-list-by-server-status/server-info/status-icon.vue new file mode 100644 index 0000000..25dfb4e --- /dev/null +++ b/src/views/components/server-list-by-server-status/server-info/status-icon.vue @@ -0,0 +1,47 @@ + + + + + diff --git a/src/views/components/server-list-by-server-status/server-info/system-os.vue b/src/views/components/server-list-by-server-status/server-info/system-os.vue new file mode 100644 index 0000000..14f6686 --- /dev/null +++ b/src/views/components/server-list-by-server-status/server-info/system-os.vue @@ -0,0 +1,39 @@ + + + + + diff --git a/src/views/components/server-list-by-server-status/server-info/transfer.vue b/src/views/components/server-list-by-server-status/server-info/transfer.vue new file mode 100644 index 0000000..3e9a45a --- /dev/null +++ b/src/views/components/server-list-by-server-status/server-info/transfer.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/src/views/components/server-list-by-server-status/server-status.js b/src/views/components/server-list-by-server-status/server-status.js new file mode 100644 index 0000000..fbe60b6 --- /dev/null +++ b/src/views/components/server-list-by-server-status/server-status.js @@ -0,0 +1,438 @@ +/** + * ServerStatus风格的列表列配置 + */ +import { + h, +} from 'vue'; + +// import * as hostUtils from '@/utils/host'; +import handleServerStatus from '@/views/composable/server-status'; +import handleServerInfo from '@/views/composable/server-info'; +import handleServerRealTime from '@/views/composable/server-real-time'; +import handleServerBillAndPlan from '@/views/composable/server-bill-and-plan'; + +import ServerStatusProgress from '@/views/components/server/server-status-progress.vue'; +import StatusIcon from '@/views/components/server-list-by-server-status/server-info/status-icon.vue'; +import SystemOS from '@/views/components/server-list-by-server-status/server-info/system-os.vue'; +import Country from '@/views/components/server-list-by-server-status/server-info/country.vue'; +import NetSpeed from '@/views/components/server-list-by-server-status/server-info/net-speed.vue'; +import Transfer from '@/views/components/server-list-by-server-status/server-info/transfer.vue'; +import Conns from '@/views/components/server-list-by-server-status/server-info/conns.vue'; + +const COLUMN_MAP = Object.freeze({ + status: { + label: '状态', + width: 40, + }, + name: { + label: '名称', + minWidth: 100, + align: 'left', + }, + config: { + label: '规格', + width: 80, + align: 'left', + }, + system: { + label: '系统', + width: 90, + align: 'left', + }, + country: { + label: '地区', + width: 60, + align: 'left', + }, + duration: { + label: '在线', + width: 60, + align: 'left', + }, + load: { + label: '负载', + width: 45, + align: 'center', + }, + speeds: { + label: '网速', + width: 112, + align: 'center', + }, + inSpeed: { + label: '入网', + width: 60, + align: 'left', + }, + outSpeed: { + label: '出网', + width: 60, + align: 'left', + }, + transfer: { + label: '流量', + width: 112, + align: 'center', + }, + inTransfer: { + label: '入网流量', + width: 60, + align: 'left', + }, + outTransfer: { + label: '出网流量', + width: 60, + align: 'left', + }, + conns: { + label: '连接', + width: 72, + align: 'center', + }, + tcp: { + label: 'TCP', + width: 40, + align: 'left', + }, + udp: { + label: 'UDP', + width: 40, + align: 'left', + }, + cpu: { + label: 'CPU', + width: 80, + align: 'center', + }, + cpuText: { + valProp: 'cpu', + label: 'CPU', + width: 40, + align: 'center', + }, + mem: { + label: '内存', + width: 80, + align: 'center', + }, + memText: { + valProp: 'mem', + label: '内存', + width: 40, + align: 'center', + }, + swap: { + label: '交换', + width: 80, + align: 'center', + }, + swapText: { + valProp: 'swap', + label: '交换', + width: 40, + align: 'center', + }, + disk: { + label: '硬盘', + width: 80, + align: 'center', + }, + diskText: { + valProp: 'disk', + label: '硬盘', + width: 40, + align: 'center', + }, + billing: { + label: '价格', + width: 110, + align: 'right', + }, + remainingTime: { + label: '剩余', + width: 70, + align: 'right', + }, +}); + +/** + * 默认列配置 + */ +// eslint-disable-next-line max-len, vue/max-len +const DEFAULT_COLUMNS = 'status,name,country,system,config,duration,speeds,transfer,conns,load,cpuText,memText,diskText,billing,remainingTime'; + +/** + * 需要实时更新的数据 + */ +const RELD_TIME_DATA = [ + 'speeds', 'inSpeed', 'outSpeed', + 'transfer', 'inTransfer', 'outTransfer', + 'conns', 'tcp', 'udp', + 'duration', 'load', +]; + +/** + * 获取列配置 + * @param {string} columnsTpls 列配置模板 + * @returns {Object} 列配置 + * @property {Array} columns 列配置 + */ +export const getColumnPropsConfig = (tpls = DEFAULT_COLUMNS) => { + const tplList = tpls.split(','); + const columnList = []; + tplList.forEach((tpl) => { + if (COLUMN_MAP[tpl]) { + columnList.push({ + prop: tpl, + ...COLUMN_MAP[tpl], + }); + } + }); + return columnList; +}; + +/** + * 将服务器数据转换为表格数据 + * @param {Object} server 服务器数据 + * @returns {Object} 表格数据 + */ +export const handleServerItemData = (params) => { + const { + column, + server, + realTimeData, + progressData, + billAndPlan, + } = params || {}; + switch (column.prop) { + case 'status': + return { + type: 'component', + component: h(StatusIcon, { info: server }), + originalData: params, + }; + case 'name': + return { + type: 'text', + value: server.Name, + originalData: params, + }; + case 'config': + { + const { cpuAndMemAndDisk } = handleServerInfo({ + props: { + info: server, + }, + originalData: params, + }); + return { + type: 'text', + value: cpuAndMemAndDisk, + originalData: params, + }; + } + case 'system': + return { + type: 'component', + component: h(SystemOS, { info: server }), + originalData: params, + }; + case 'country': + return { + type: 'component', + component: h(Country, { info: server }), + originalData: params, + }; + case 'speeds': + return { + type: 'component', + component: h(NetSpeed, { realTimeData }), + originalData: params, + }; + case 'transfer': + return { + type: 'component', + component: h(Transfer, { realTimeData }), + originalData: params, + }; + case 'conns': + return { + type: 'component', + component: h(Conns, { realTimeData }), + originalData: params, + }; + case 'cpu': + case 'mem': + case 'disk': + case 'swap': + { + const progressItem = progressData[column.prop]; + return { + type: 'component', + component: h(ServerStatusProgress, { + type: column.prop, + used: progressItem?.used || 0, + colors: progressItem?.colors || {}, + valText: progressItem?.valPercent || '', + }), + originalData: params, + }; + } + case 'cpuText': + case 'memText': + case 'diskText': + case 'swapText': + { + const progressItem = progressData[column.valProp]; + return { + prop: column.prop, + type: 'text', + value: parseFloat(progressItem?.used || 0).toFixed(1), + unit: '%', + text: progressItem?.valPercent || '', + originalData: params, + }; + } + case 'billing': + case 'remainingTime': + { + const item = billAndPlan?.value?.[column.prop]; + const texts = []; + if (item?.value) { + texts.push(item.value || '-'); + } + if (item?.cycleLabel) { + texts.push(item.cycleLabel); + } + return { + prop: column.prop, + type: 'text', + text: texts.join('/'), + originalData: params, + }; + } + default: { + if (RELD_TIME_DATA.includes(column.prop) && realTimeData[column.prop]) { + const item = realTimeData[column.prop]; + return { + prop: column.prop, + type: 'text', + text: item?.text, + value: item?.value, + unit: item?.unit, + originalData: params, + }; + } + return { + prop: column.prop, + type: 'text', + value: '-', + originalData: params, + }; + } + } +}; + +/** + * 将服务器数据转换为表格数据 + * @param {Object} server 服务器数据 + * @param {Array} columns 列配置 + * @returns {Array} 表格数据 + */ +export const handleServerListColumn = (serverList, columnTpls = DEFAULT_COLUMNS) => { + const columnProps = getColumnPropsConfig(columnTpls); + const tpls = columnProps.map((column) => column.valProp || column.prop).join(','); + const hasBilling = columnTpls.includes('billing'); + const hasRemainingTime = columnTpls.includes('remainingTime'); + let showBilling = false; + let showRemainingTime = false; + const list = serverList.map((server) => { + // 负载\网速\流量\在线等 + const realTimeResult = handleServerRealTime({ + props: { + info: server, + }, + serverRealTimeListTpls: tpls, + }); + const realTimeData = {}; + realTimeResult?.serverRealTimeList?.value?.forEach?.((item) => { + if (item.show) { + const text = [item.value]; + if (item.unit) { + text.push(item.unit); + } + realTimeData[item.key] = { + value: item.value, + unit: item.unit, + text: text.join(''), + item, + }; + } else { + realTimeData[item.key] = { + text: '-', + item, + }; + } + }); + // CPU\内存\硬盘\交换 进度条 + const { + serverStatusList, + } = handleServerStatus({ + props: { + info: server, + }, + statusListTpl: tpls, + statusListItemContent: false, + }); + const progressData = {}; + serverStatusList.value?.forEach?.((item) => { + progressData[item.type] = item; + }); + let billAndPlan = null; + if (hasBilling || hasRemainingTime) { + const result = handleServerBillAndPlan({ + props: { + info: server, + }, + }); + billAndPlan = result.billAndPlan; + if (billAndPlan?.value?.billing) { + showBilling = true; + } + if (billAndPlan?.value?.remainingTime) { + console.log('remainingTime', billAndPlan.value.remainingTime); + showRemainingTime = true; + } + } + + const columnData = []; + columnProps.forEach((columnItem) => { + columnData.push({ + ...columnItem, + data: handleServerItemData({ + column: columnItem, + server, + realTimeData, + progressData, + billAndPlan, + }), + }); + }); + + return { + info: server, + columnData, + computedData: { + realTimeData, + progressData, + billAndPlan, + }, + }; + }); + return { + list, + columnProps, + showBilling, + showRemainingTime, + }; +}; diff --git a/src/views/components/server-list-by-server-status/table/td.vue b/src/views/components/server-list-by-server-status/table/td.vue new file mode 100644 index 0000000..dc7eff6 --- /dev/null +++ b/src/views/components/server-list-by-server-status/table/td.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/src/views/components/server-list-by-server-status/table/th.vue b/src/views/components/server-list-by-server-status/table/th.vue new file mode 100644 index 0000000..1bd6efc --- /dev/null +++ b/src/views/components/server-list-by-server-status/table/th.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/src/views/components/server-list/server-list-warp.vue b/src/views/components/server-list/server-list-warp.vue index 86c1e4f..bcabf3f 100644 --- a/src/views/components/server-list/server-list-warp.vue +++ b/src/views/components/server-list/server-list-warp.vue @@ -7,6 +7,7 @@ :class="{ 'server-list--row': showListRow, 'server-list--card': showListCard, + 'server-list--status': showListByServerStatus, }" > @@ -17,6 +18,7 @@ :class="{ 'server-list--row': showListRow, 'server-list--card': showListCard, + 'server-list--status': showListByServerStatus, }" > @@ -41,6 +43,10 @@ defineProps({ type: Boolean, default: false, }, + showListByServerStatus: { + type: Boolean, + default: false, + }, }); @@ -94,6 +100,18 @@ defineProps({ margin: auto; } +.server-list-container.server-list--status { + --list-padding: 20px; + --list-gap-size: 12px; + position: relative; + display: flex; + flex-direction: column; + gap: var(--list-gap-size); + width: var(--list-container-width); + padding: 0 var(--list-padding); + margin: auto; +} + .list-move, .list-enter-active, .list-leave-active { diff --git a/src/views/components/server/server-status-progress.vue b/src/views/components/server/server-status-progress.vue index 60fca78..7ed2c0a 100644 --- a/src/views/components/server/server-status-progress.vue +++ b/src/views/components/server/server-status-progress.vue @@ -12,7 +12,10 @@ class="progress-bar-label" :title="label + '使用' + used + '%'" > - + {{ label }}: diff --git a/src/views/composable/server-real-time.js b/src/views/composable/server-real-time.js index 97068a4..74d583a 100644 --- a/src/views/composable/server-real-time.js +++ b/src/views/composable/server-real-time.js @@ -221,6 +221,18 @@ export default (params) => { value: transfer.value?.value, unit: transfer.value?.unit, show: validate.isSet(transfer.value?.value), + data: { + in: { + value: inTransfer.value?.value, + unit: inTransfer.value?.unit, + show: validate.isSet(inTransfer.value?.value), + }, + out: { + value: outTransfer.value?.value, + unit: outTransfer.value?.unit, + show: validate.isSet(outTransfer.value?.value), + }, + }, }; case 'inTransfer': return { @@ -263,6 +275,18 @@ export default (params) => { `${netOutSpeed.value?.value}${netOutSpeed.value?.unit}`, ].join('|'), show: validate.isSet(netInSpeed.value?.value) && validate.isSet(netOutSpeed.value?.value), + data: { + in: { + value: netInSpeed.value?.value, + unit: netInSpeed.value?.unit, + show: validate.isSet(netInSpeed.value?.value), + }, + out: { + value: netOutSpeed.value?.value, + unit: netOutSpeed.value?.unit, + show: validate.isSet(netOutSpeed.value?.value), + }, + }, }; case 'load': return { @@ -271,12 +295,49 @@ export default (params) => { value: (props.info.State?.Load1 || 0).toFixed(2), show: validate.isSet(props.info.State?.Load1), }; + case 'loads': + { + const loads = []; + loads.push((props.info.State?.Load1 || 0).toFixed(2)); + loads.push((props.info.State?.Load5 || 0).toFixed(2)); + loads.push((props.info.State?.Load15 || 0).toFixed(2)); + return { + key, + label: '负载', + value: loads.join(','), + show: loads.some((load) => validate.isSet(load)), + data: { + load1: { + value: (props.info.State?.Load1 || 0).toFixed(2), + show: validate.isSet(props.info.State?.Load1), + }, + load5: { + value: (props.info.State?.Load5 || 0).toFixed(2), + show: validate.isSet(props.info.State?.Load5), + }, + load15: { + value: (props.info.State?.Load15 || 0).toFixed(2), + show: validate.isSet(props.info.State?.Load15), + }, + }, + }; + } case 'conns': return { key, label: '连接', value: `${props.info.State?.TcpConnCount || 0}|${props.info.State?.UdpConnCount || 0}`, show: true, + data: { + tcp: { + value: props.info.State?.TcpConnCount || 0, + show: validate.isSet(props.info.State?.TcpConnCount), + }, + udp: { + value: props.info.State?.UdpConnCount || 0, + show: validate.isSet(props.info.State?.UdpConnCount), + }, + }, }; case 'tcp': return { @@ -292,6 +353,7 @@ export default (params) => { value: props.info.State?.UdpConnCount || 0, show: validate.isSet(props.info.State?.UdpConnCount), }; + // 入网和出网 case 'I-A-O': return { key, @@ -314,6 +376,7 @@ export default (params) => { ], show: validate.isSet(netInSpeed.value?.value) && validate.isSet(netOutSpeed.value?.value), }; + // 负载和进程 case 'L-A-P': return { key, @@ -334,6 +397,7 @@ export default (params) => { ], show: validate.isSet(props.info.State?.Load1) || validate.isSet(props.info.State?.ProcessCount), }; + // 连接 TCP和UDP case 'T-A-U': return { key, @@ -354,6 +418,7 @@ export default (params) => { ], show: validate.isSet(props.info.State?.TcpConnCount) || validate.isSet(props.info.State?.UdpConnCount), }; + // 在线和流量 case 'D-A-T': return { key, diff --git a/src/views/home.vue b/src/views/home.vue index 491125b..b3d6e00 100644 --- a/src/views/home.vue +++ b/src/views/home.vue @@ -40,6 +40,7 @@ /> + + + + + + { const showListCard = computed(() => { if (windowWidth.value > 1024) { if (config.nazhua.listServerItemTypeToggle) { - return listType.value !== 'row'; + return listType.value === 'card'; } - return config.nazhua.listServerItemType !== 'row'; + return config.nazhua.listServerItemType === 'card'; } return true; }); +const showListByServerStatus = computed(() => { + if (windowWidth.value > 1024) { + if (config.nazhua.listServerItemTypeToggle) { + return listType.value === 'status'; + } + return config.nazhua.listServerItemType === 'status'; + } + return false; +}); const showFilter = computed(() => config.nazhua.hideFilter !== true); const filterFormData = ref({ @@ -217,14 +239,19 @@ watch(() => serverCount.value, () => { const listTypeOptions = computed(() => [{ key: 'card', - label: '卡片', + label: '卡片模式', value: 'card', icon: 'ri-gallery-view-2', }, { key: 'row', - label: '列表', + label: '列表模式', value: 'row', icon: 'ri-list-view', +}, { + key: 'status', + label: 'ServerStatus模式', + value: 'status', + icon: 'ri-server-line', }]); const filterServerList = computed(() => {