Update: (步骤备份)完善系统监控

This commit is contained in:
wojiaoyishang
2025-01-21 20:58:37 +08:00
parent 3bb59d3786
commit de36a9936a
10 changed files with 583 additions and 167 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ from io import BytesIO
from flask import session, make_response from flask import session, make_response
from applications.common.utils.gen_captcha import vieCode from applications.common.utils.captcha import vieCode
# 生成验证码 # 生成验证码
+56
View File
@@ -0,0 +1,56 @@
import time
cache_dict = {}
def cache_set_internal(key, value, expired=5):
"""
程序内部实现的记录缓存,用于简单、体量不大的缓存记录,在程序结束后销毁。对于高速、体量大的环境请配置 Redis 等服务自行记录。
记录缓存,存储键值对,并记录当前时间作为缓存的时间戳。
:param key: 键
:param value: 值
:param expired: 过期时间(秒),默认5秒
"""
cache_dict[key] = {
'value': value,
'expired_time': time.time() + expired
}
def cache_get_internal(key):
"""
获取缓存,根据键从缓存中获取值,并检查是否过期。
:param key: 键
:return: 如果缓存存在且未过期,返回缓存的值;否则返回 None
"""
if key in cache_dict:
cache_item = cache_dict[key]
if time.time() < cache_item['expired_time']:
return cache_item['value']
else:
# 如果缓存已过期,删除该缓存
del cache_dict[key]
return None
def cache_auto_internal(key, call, expired=5):
"""
如果缓存存在直接返回缓存内容,缓存不存在或者过期执行 call 函数,并取得返回值记录并返回。
:param key: 键
:param call: 获取新值的地方
:param expired: 过期时间(秒),默认5秒
"""
data = cache_get_internal(key)
if data is not None:
return data
data = call()
cache_set_internal(key, data, expired)
return data
@@ -14,7 +14,7 @@ class vieCode:
__inCurve = True # 是否画干扰线 __inCurve = True # 是否画干扰线
__inNoise = True # 是否画干扰点 __inNoise = True # 是否画干扰点
__type = 2 # 验证码类型 1、纯字母 2、数字字母混合 __type = 2 # 验证码类型 1、纯字母 2、数字字母混合
__fontPatn = 'applications/common/utils/fonts/1.ttf' # 字体 __fontPatn = 'applications/common/utils/fonts/captcha.ttf' # 字体
def GetCodeImage(self, size=80, length=4): def GetCodeImage(self, size=80, length=4):
'''获取验证码图片 '''获取验证码图片
+26 -26
View File
@@ -11,7 +11,7 @@ def str_escape(s):
between = validators.between between = validators.between
''' """
验证数字是否介于最小值和/或最大值之间。 验证数字是否介于最小值和/或最大值之间。
这将适用于任何类似的类型,如浮点数、小数和日期,而不仅仅是整数。 这将适用于任何类似的类型,如浮点数、小数和日期,而不仅仅是整数。
between(value, min=None, max=None) between(value, min=None, max=None)
@@ -35,10 +35,10 @@ between(value, min=None, max=None)
... min=datetime(1999, 11, 11) ... min=datetime(1999, 11, 11)
... ) ... )
True True
''' """
domain = validators.domain domain = validators.domain
''' """
返回给定值是否为有效域 返回给定值是否为有效域
如果值是有效域名,则此函数返回 True ,否则返回 ValidationFailure 如果值是有效域名,则此函数返回 True ,否则返回 ValidationFailure
domain(value) domain(value)
@@ -49,10 +49,10 @@ domain(value)
>>> domain('example.com/') >>> domain('example.com/')
ValidationFailure(func=domain, ...) ValidationFailure(func=domain, ...)
''' """
email = validators.email email = validators.email
''' """
验证电子邮件地址。验证成功时返回 True ,验证失败时返回 验证电子邮件地址。验证成功时返回 True ,验证失败时返回
>>> email('someone@example.com') >>> email('someone@example.com')
@@ -60,10 +60,10 @@ email = validators.email
>>> email('bogus@@') >>> email('bogus@@')
ValidationFailure(func=email, ...) ValidationFailure(func=email, ...)
''' """
iban = validators.iban iban = validators.iban
''' """
返回给定值是否为有效的IBAN代码。 返回给定值是否为有效的IBAN代码。
如果值是有效的IBAN,则此函数返回 True ,否则返回 ValidationFailure 。 如果值是有效的IBAN,则此函数返回 True ,否则返回 ValidationFailure 。
@@ -72,10 +72,10 @@ iban = validators.iban
>>> iban('123456') >>> iban('123456')
ValidationFailure(func=iban, ...) ValidationFailure(func=iban, ...)
''' """
ipv4 = validators.ipv4 ipv4 = validators.ipv4
''' """
返回给定值是否为有效的IPv4地址。 返回给定值是否为有效的IPv4地址。
>>> ipv4('123.0.0.7') >>> ipv4('123.0.0.7')
@@ -83,20 +83,20 @@ ipv4 = validators.ipv4
>>> ipv4('900.80.70.11') >>> ipv4('900.80.70.11')
ValidationFailure(func=ipv4, args={'value': '900.80.70.11'}) ValidationFailure(func=ipv4, args={'value': '900.80.70.11'})
''' """
ipv6 = validators.ipv6 ipv6 = validators.ipv6
''' """
返回给定值是否为有效的IP版本6地址。 返回给定值是否为有效的IP版本6地址。
>>> ipv6('abcd:ef::42:1') >>> ipv6('abcd:ef::42:1')
True True
>>> ipv6('abc.0.0.1') >>> ipv6('abc.0.0.1')
ValidationFailure(func=ipv6, args={'value': 'abc.0.0.1'}) ValidationFailure(func=ipv6, args={'value': 'abc.0.0.1'})
''' """
length = validators.length length = validators.length
''' """
返回给定字符串的长度是否在指定范围内。 返回给定字符串的长度是否在指定范围内。
>>> length('something', min=2) >>> length('something', min=2)
True True
@@ -106,10 +106,10 @@ length = validators.length
>>> length('something', max=5) >>> length('something', max=5)
ValidationFailure(func=length, ...) ValidationFailure(func=length, ...)
''' """
mac_address = validators.mac_address mac_address = validators.mac_address
''' """
返回给定值是否为有效MAC地址。 返回给定值是否为有效MAC地址。
如果该值是有效的MAC地址,则此函数返回 True ,否则返回 ValidationFailure 。 如果该值是有效的MAC地址,则此函数返回 True ,否则返回 ValidationFailure 。
@@ -118,10 +118,10 @@ mac_address = validators.mac_address
>>> mac_address('00:00:00:00:00') >>> mac_address('00:00:00:00:00')
ValidationFailure(func=mac_address, args={'value': '00:00:00:00:00'}) ValidationFailure(func=mac_address, args={'value': '00:00:00:00:00'})
''' """
slug = validators.slug slug = validators.slug
''' """
验证给定值是否为有效的块。 验证给定值是否为有效的块。
有效的短信息只能包含字母数字字符、连字符和下划线。 有效的短信息只能包含字母数字字符、连字符和下划线。
>>> slug('my.slug') >>> slug('my.slug')
@@ -129,15 +129,15 @@ slug = validators.slug
>>> slug('my-slug-2134') >>> slug('my-slug-2134')
True True
''' """
#truthy = validators.truthy #truthy = validators.truthy
''' """
验证给定值不是错误值。 验证给定值不是错误值。
''' """
url = validators.url url = validators.url
''' """
返回给定值是否为有效URL。 返回给定值是否为有效URL。
如果值是有效URL,则此函数返回 True ,否则返回 ValidationFailure 。 如果值是有效URL,则此函数返回 True ,否则返回 ValidationFailure 。
@@ -152,10 +152,10 @@ url = validators.url
>>> url('http://10.0.0.1', public=True) >>> url('http://10.0.0.1', public=True)
ValidationFailure(func=url, ...) ValidationFailure(func=url, ...)
''' """
uuid = validators.uuid uuid = validators.uuid
''' """
返回给定值是否为有效UUID。 返回给定值是否为有效UUID。
如果值是有效的UUID,则此函数返回 True ,否则返回 ValidationFailure 。 如果值是有效的UUID,则此函数返回 True ,否则返回 ValidationFailure 。
@@ -164,7 +164,7 @@ uuid = validators.uuid
>>> uuid('2bc1c94f 0deb-43e9-92a1-4775189ec9f8') >>> uuid('2bc1c94f 0deb-43e9-92a1-4775189ec9f8')
ValidationFailure(func=uuid, ...) ValidationFailure(func=uuid, ...)
''' """
@validator @validator
@@ -172,7 +172,7 @@ def even(value):
return not (value % 2) return not (value % 2)
''' """
一个装饰器,它使给定的函数验证器 一个装饰器,它使给定的函数验证器
每当给定函数被调用并返回 False 值时,这个装饰器返回 ValidationFailure 对象。 每当给定函数被调用并返回 False 值时,这个装饰器返回 ValidationFailure 对象。
>>> @validator >>> @validator
@@ -184,4 +184,4 @@ True
>>> even(5) >>> even(5)
ValidationFailure(func=even, args={'value': 5}) ValidationFailure(func=even, args={'value': 5})
''' """
+114 -45
View File
@@ -1,47 +1,33 @@
import os import os
import platform
import re import re
import sys import sys
import time import time
import psutil
import platform
from datetime import datetime from datetime import datetime
import psutil from flask import Blueprint, render_template
from flask import Blueprint, render_template, jsonify
from applications.common.utils.http import table_api, success_api
from applications.common.utils.rights import authorize from applications.common.utils.rights import authorize
from applications.common.utils.cache import cache_auto_internal
bp = Blueprint('adminMonitor', __name__, url_prefix='/monitor') bp = Blueprint('adminMonitor', __name__, url_prefix='/monitor')
# 系统监控 def get_disk_partitions_list():
@bp.get('/')
@authorize("system:monitor:main")
def main():
# 主机名称
hostname = platform.node()
# 系统版本
system_version = platform.platform()
# python版本
python_version = platform.python_version()
# 逻辑cpu数量
cpu_count = psutil.cpu_count()
# cpu使用率
cpus_percent = psutil.cpu_percent(interval=0.1, percpu=False) # percpu 获取主使用率
# 内存
memory_information = psutil.virtual_memory()
# 内存使用率
memory_usage = memory_information.percent
memory_used: int = memory_information.used
memory_total: int = memory_information.total
memory_free: int = memory_information.free
# 磁盘信息
disk_partitions_list = [] disk_partitions_list = []
# 判断是否在容器中 # 判断是否在容器中
if not os.path.exists('/.dockerenv'): if not os.path.exists('/.dockerenv'):
disk_partitions = psutil.disk_partitions() disk_partitions = psutil.disk_partitions()
for i in disk_partitions: for i in disk_partitions:
try:
a = psutil.disk_usage(i.device) a = psutil.disk_usage(i.device)
except PermissionError:
continue
disk_partitions_dict = { disk_partitions_dict = {
'device': i.device, 'device': i.device,
'fstype': i.fstype, 'fstype': i.fstype,
@@ -51,30 +37,58 @@ def main():
'percent': a.percent 'percent': a.percent
} }
disk_partitions_list.append(disk_partitions_dict) disk_partitions_list.append(disk_partitions_dict)
else:
try:
usage = psutil.disk_usage('/')
except PermissionError:
pass
disk_partitions_list.append({
'device': '/', # 设备名称(根文件系统)
'fstype': psutil.disk_partitions()[0].fstype, # 文件系统类型
'total': usage.total, # 总容量(字节)
'used': usage.used, # 已用空间(字节)
'free': usage.free, # 可用空间(字节)
'percent': usage.percent # 使用百分比
})
return disk_partitions_list
def get_basic_info():
# 主机名称
hostname = platform.node()
# 系统版本
system_version = platform.platform()
# Python 版本
python_version = platform.python_version()
# 开机时间 # 开机时间
boot_time = datetime.fromtimestamp(psutil.boot_time()).replace(microsecond=0) boot_time = datetime.fromtimestamp(psutil.boot_time()).replace(microsecond=0)
up_time = datetime.now().replace(microsecond=0) - boot_time up_time = datetime.now().replace(microsecond=0) - boot_time
up_time_list = re.split(r':', str(up_time)) up_time_list = re.split(r':', str(up_time))
up_time_format = "{} 小时 {} 分钟 {}".format(up_time_list[0], up_time_list[1], up_time_list[2]) up_time_format = "{} 小时 {} 分钟 {}".format(up_time_list[0], up_time_list[1], up_time_list[2])
up_time_format = up_time_format.replace("days,", "")
return {
'hostname': hostname,
'system_version': system_version,
'python_version': python_version,
'boot_time': boot_time,
'up_time_format': up_time_format
}
# 系统监控
@bp.get('/')
@authorize("system:monitor:main")
def main():
# 当前时间 # 当前时间
time_now = time.strftime('%H:%M:%S ', time.localtime(time.time())) time_now = time.strftime('%H:%M:%S ', time.localtime(time.time()))
return render_template( return render_template(
'system/monitor.html', 'system/monitor.html',
hostname=hostname, time_now=time_now,
system_version=system_version, **get_basic_info()
python_version=python_version,
cpus_percent=cpus_percent,
memory_usage=memory_usage,
cpu_count=cpu_count,
memory_used=memory_used,
memory_total=memory_total,
memory_free=memory_free,
boot_time=boot_time,
up_time_format=up_time_format,
disk_partitions_list=disk_partitions_list,
time_now=time_now
) )
@@ -82,19 +96,74 @@ def main():
@bp.get('/polling') @bp.get('/polling')
@authorize("system:monitor:main") @authorize("system:monitor:main")
def ajax_polling(): def ajax_polling():
# 获取cpu使用率 # 获取 CPU 核心数
cpus_percent = psutil.cpu_percent(interval=0.1, percpu=False) # percpu 获取主使用率 cpu_count = cache_auto_internal('cpu_count', lambda: psutil.cpu_count(), expired=999999999)
# 获取内存使用率
memory_information = psutil.virtual_memory() # 获取 CPU 使用率
cpus_percent = cache_auto_internal('cpus_percent',
lambda: psutil.cpu_percent(interval=1, percpu=False),
expired=5)
# 每个 CPU 的使用率
cpu_percent_per_core = cache_auto_internal('cpu_percent_per_core',
lambda: list(enumerate(psutil.cpu_percent(interval=1, percpu=True))),
expired=5)
# 获取空闲率、等待率
cpu_times_percent = cache_auto_internal('cpu_times_percent',
lambda: psutil.cpu_times_percent(interval=1, percpu=False),
expired=5)
# 内存信息
memory_information = cache_auto_internal('memory_information',
psutil.virtual_memory,
expired=5)
# 硬盘信息
disk_partitions_list = cache_auto_internal('disk_partitions_list',
get_disk_partitions_list,
expired=5)
# 系统信息
basic_info = cache_auto_internal('basic_info',
get_basic_info,
expired=5)
memory_usage = memory_information.percent memory_usage = memory_information.percent
time_now = time.strftime('%H:%M:%S ', time.localtime(time.time())) memory_used = memory_information.used
return jsonify(cups_percent=cpus_percent, memory_used=memory_usage, time_now=time_now) memory_total = memory_information.total
memory_free = memory_information.free
cpu_idle_percent = cpu_times_percent.idle
if hasattr(cpu_times_percent, 'iowait'):
cpu_wait_percent = cpu_times_percent.iowait
else:
cpu_wait_percent = "-"
return table_api(msg="请求成功",
count=0,
data={
'cpu_count': cpu_count,
'cpus_percent': cpus_percent,
'cpu_idle_percent': cpu_idle_percent,
'cpu_wait_percent': cpu_wait_percent,
'cpu_percent_per_core': cpu_percent_per_core,
'memory_used': memory_used,
'memory_total': memory_total,
'memory_free': memory_free,
'memory_usage': memory_usage,
'disk_partitions_list': disk_partitions_list,
'time_now': time.strftime('%H:%M:%S', time.localtime(time.time())),
'basic_info': basic_info
})
# 关闭程序 # 关闭程序
@bp.get('/kill') @bp.get('/kill')
@authorize("system:monitor:main") @authorize("system:monitor:main")
def kill(): def kill():
# 注:若是多 worker 则不生效
return success_api(msg="关闭命令已发送,请修改代码以生效。")
for proc in psutil.process_iter(): for proc in psutil.process_iter():
if proc.pid == os.getpid(): if proc.pid == os.getpid():
proc.kill() proc.kill()
+187
View File
@@ -0,0 +1,187 @@
.pear-container {
background-color: whitesmoke;
margin: 10px;
}
.pear-card {
width: 100%;
height: 66px;
background-color: #F8F8F8;
display: inline-block;
border-radius: 5px;
text-align: center;
margin-bottom: 3px;
}
.pear-card:hover,
.pear-card2:hover {
box-shadow: 2px 0 8px 0 lightgray !important;
}
.pear-card2 {
width: 100%;
height: 90px;
background-color: #F8F8F8;
display: inline-block;
border-radius: 5px;
text-align: center;
margin-bottom: 3px;
}
.pear-card2 i {
font-size: 30px;
height: 90px;
line-height: 90px;
}
.pear-card i {
font-size: 30px;
height: 66px;
line-height: 66px;
}
.layui-col-md3 {
text-align: center;
}
.pear-card-title {
margin-top: 3px;
}
.person img {
width: 90px;
height: 90px;
border-radius: 4px;
margin-top: 8px;
margin-left: 8px;
}
.pear-card2 .count {
color: var(--global-primary-color);
font-size: 30px;
margin-top: 12px;
}
.pear-card2 .title {
color: gray;
font-size: 14px;
margin-top: 14px;
}
.pear-card-status {
padding: 0 10px 10px;
}
.pear-card-status li {
position: relative;
padding: 10px 0;
border-bottom: 1px solid #EEE;
}
.pear-card-status li h3 {
padding-bottom: 5px;
font-weight: 700;
}
.pear-card-status li p {
padding-bottom: 10px;
padding-top: 3px;
}
.pear-card-status li > span {
color: #999;
height: 24px;
line-height: 24px;
}
.pear-reply {
position: absolute;
right: 20px;
height: 24px;
line-height: 24px;
}
.person .title {
font-size: 17px;
font-weight: 600;
margin-left: 18px;
margin-top: 16px;
position: absolute;
display: inline-block;
}
.person .desc {
font-size: 16px;
font-weight: 600;
margin-left: 115px;
margin-top: -30px;
position: absolute;
display: inline-block;
}
#tooltip {
opacity: 0; /* 默认完全透明 */
transition: opacity 0.3s ease-in-out; /* 添加淡入淡出动画 */
position: absolute;
border-radius: 5px;
padding: 10px;
z-index: 1000;
}
#tooltip.show {
opacity: 1; /* 完全显示 */
}
#tooltip .layui-card-body ul {
display: grid; /* 启用 Grid 布局 */
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); /* 自动填充列 */
gap: 5px; /* 设置子元素之间的间距 */
padding: 0; /* 移除默认的 ul 内边距 */
margin: 0; /* 移除默认的 ul 外边距 */
list-style: none; /* 移除列表项的默认样式 */
}
#tooltip .layui-card-body ul li span {
width: 8em;
background-color: #E7EEFC !important;
color: #6197F8 !important;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.progress-ring {
position: relative;
display: inline-block;
width: 120px;
height: 120px;
text-align: center;
}
.progress-ring .circle {
transform: rotate(-90deg);
}
.progress-ring .circle-bg {
fill: none;
stroke: #e6e6e6; /* 背景色 */
stroke-width: 8; /* 环的宽度(细一点) */
}
.progress-ring .circle-progress {
fill: none;
stroke: var(--global-primary-color); /* 使用 CSS 变量 */
stroke-width: 8; /* 环的宽度(细一点) */
stroke-dasharray: 339.292;
stroke-dashoffset: calc(339.292 - (339.292 * var(--progress) / 100));
transition: stroke-dashoffset 1s;
}
.progress-ring .progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 18px;
font-weight: bold;
color: #333;
}
@@ -15,3 +15,4 @@
@import url("module/dtree/dtree.css"); @import url("module/dtree/dtree.css");
@import url("module/layer.css"); @import url("module/layer.css");
@import url("module/layout.css"); @import url("module/layout.css");
@import url("module/popover.min.css");
-13
View File
@@ -1,13 +0,0 @@
{% macro memory_format(memory) %}
{%- if memory > 1024 ** 4 * 2 -%}
{{- (memory / 1024 ** 4) | round(2) -}}TB
{% elif memory > 1024 ** 3 * 2 %}
{{- (memory / 1024 ** 3) | round(2) -}}GB
{% elif memory > 1024 ** 2 * 2 %}
{{- (memory / 1024 ** 2) | round(2) -}}MB
{% elif memory > 1024 ** 1 * 2 %}
{{- (memory / 1024 ** 1) | round(2) -}}KB
{% else %}
{{- memory -}}B
{% endif %}
{% endmacro %}
+194 -78
View File
@@ -1,16 +1,15 @@
{% from 'system/common/memory.html' import memory_format %}
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>首页</title> <title>首页</title>
{% include 'system/common/header.html' %} {% include 'system/common/header.html' %}
<link rel="stylesheet" href="{{ url_for('static', filename='system/admin/css/other/console2.css') }}"/> <link rel="stylesheet" href="{{ url_for('static', filename='system/admin/css/other/monitor.css') }}"/>
</head> </head>
<body class="pear-container"> <body class="pear-container">
<div class="layui-row layui-col-space10"> <div class="layui-row layui-col-space10">
<div class="layui-col-md8"> <div class="layui-col-md8">
<div class="layui-row layui-col-space10"> <div class="layui-row layui-col-space10">
<div class="layui-col-md6"> <div class="layui-col-md6" id="host-info-card">
<div class="layui-card"> <div class="layui-card">
<div class="layui-card-header"> <div class="layui-card-header">
主机信息 主机信息
@@ -20,25 +19,25 @@
<div class="layui-col-md6 layui-col-sm6 layui-col-xs6"> <div class="layui-col-md6 layui-col-sm6 layui-col-xs6">
<div class="pear-card2"> <div class="pear-card2">
<div class="title">核心数</div> <div class="title">核心数</div>
<div class="count pear-text">{{ cpu_count }}</div> <div class="count pear-text" id="cpu_count">-</div>
</div> </div>
</div> </div>
<div class="layui-col-md6 layui-col-sm6 layui-col-xs6"> <div class="layui-col-md6 layui-col-sm6 layui-col-xs6">
<div class="pear-card2"> <div class="pear-card2">
<div class="title">空闲率</div> <div class="title">空闲率</div>
<div class="count pear-text"></div> <div class="count pear-text" id="cpu_idle_percent">-%</div>
</div> </div>
</div> </div>
<div class="layui-col-md6 layui-col-sm6 layui-col-xs6"> <div class="layui-col-md6 layui-col-sm6 layui-col-xs6">
<div class="pear-card2"> <div class="pear-card2">
<div class="title">等待率</div> <div class="title">等待率</div>
<div class="count pear-text"></div> <div class="count pear-text" id="cpu_wait_percent">-%</div>
</div> </div>
</div> </div>
<div class="layui-col-md6 layui-col-sm6 layui-col-xs6"> <div class="layui-col-md6 layui-col-sm6 layui-col-xs6">
<div class="pear-card2"> <div class="pear-card2">
<div class="title">使用率</div> <div class="title">使用率</div>
<div class="count pear-text">{{ cpus_percent }}%</div> <div class="count pear-text" id="cpus_percent">-%</div>
</div> </div>
</div> </div>
</div> </div>
@@ -55,25 +54,25 @@
<div class="layui-col-md6 layui-col-sm6 layui-col-xs6"> <div class="layui-col-md6 layui-col-sm6 layui-col-xs6">
<div class="pear-card2"> <div class="pear-card2">
<div class="title">空闲内存</div> <div class="title">空闲内存</div>
<div class="count pear-text">{{ memory_format(memory_free) }}</div> <div class="count pear-text" id="memory_free">-</div>
</div> </div>
</div> </div>
<div class="layui-col-md6 layui-col-sm6 layui-col-xs6"> <div class="layui-col-md6 layui-col-sm6 layui-col-xs6">
<div class="pear-card2"> <div class="pear-card2">
<div class="title">最大内存</div> <div class="title">最大内存</div>
<div class="count pear-text">{{ memory_format(memory_total) }}</div> <div class="count pear-text" id="memory_total">-</div>
</div> </div>
</div> </div>
<div class="layui-col-md6 layui-col-sm6 layui-col-xs6"> <div class="layui-col-md6 layui-col-sm6 layui-col-xs6">
<div class="pear-card2"> <div class="pear-card2">
<div class="title">已用内存</div> <div class="title">已用内存</div>
<div class="count pear-text">{{ memory_format(memory_used) }}</div> <div class="count pear-text" id="memory_used">-</div>
</div> </div>
</div> </div>
<div class="layui-col-md6 layui-col-sm6 layui-col-xs6"> <div class="layui-col-md6 layui-col-sm6 layui-col-xs6">
<div class="pear-card2"> <div class="pear-card2">
<div class="title">内存使用</div> <div class="title">内存使用</div>
<div class="count pear-text">{{ memory_usage }}%</div> <div class="count pear-text" id="memory_usage">-%</div>
</div> </div>
</div> </div>
</div> </div>
@@ -93,27 +92,6 @@
</div> </div>
</div> </div>
<div class="layui-col-md4"> <div class="layui-col-md4">
<div class="layui-card">
<div class="layui-card-header">磁盘信息</div>
<div class="layui-card-body">
<ul class="pear-card-status">
{% for disk in disk_partitions_list %}
<li>
<p>{{ disk.device }}</p>
<p>{{ disk.fstype }}</p>
磁盘大小: <span>{{ memory_format(disk.total) }}</span>&nbsp;&nbsp;
空闲大小: <span>{{ memory_format(disk.free) }}</span>&nbsp;&nbsp;
<br/>
<br/>
已经使用: <span>{{ memory_format(disk.used) }}</span>&nbsp;&nbsp;
使用率: <span>{{ disk.percent }}%</span>
<br/>
<a href="javascript:0" data-id="1" class="pear-btn pear-btn-xs pear-btn-primary pear-reply">详情</a>
</li>
{% endfor %}
</ul>
</div>
</div>
<div class="layui-card"> <div class="layui-card">
<div class="layui-card-header">主机信息</div> <div class="layui-card-header">主机信息</div>
<div class="layui-card-body"> <div class="layui-card-body">
@@ -127,47 +105,135 @@
<tbody> <tbody>
<tr> <tr>
<td>名称</td> <td>名称</td>
<td>{{ hostname }}</td> <td id="hostname">{{ hostname }}</td>
</tr> </tr>
<tr> <tr>
<td>系统</td> <td>系统</td>
<td>{{ system_version }}</td> <td id="system_version">{{ system_version }}</td>
</tr> </tr>
<tr> <tr>
<td>开机时间</td> <td>开机时间</td>
<td>{{ boot_time }}</td> <td id="boot_time">{{ boot_time }}</td>
</tr> </tr>
<tr> <tr>
<td>运行时长</td> <td>运行时长</td>
<td>{{ up_time_format }}</td> <td id="up_time_format">{{ up_time_format }}</td>
</tr> </tr>
<tr> <tr>
<td>python版本</td> <td>Python 版本</td>
<td>{{ python_version }}</td> <td id="python_version">{{ python_version }}</td>
</tr> </tr>
<tr> <tr>
<td>程序操作</td> <td>程序操作</td>
<td> <td>
<a href="javascript:kill();" <a href="javascript:kill();"
class="pear-btn pear-btn-xs pear-btn-primary">关闭程序</a> class="layui-btn layui-btn-xs layui-btn-primary">关闭程序</a>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<div class="layui-card">
<div class="layui-card-header">磁盘信息</div>
<div class="layui-card-body" id="disk-info-card">
加载中......
</div> </div>
</div> </div>
</div>
</div>
<div id="tooltip" style="background-color: unset !important;">
<div class="layui-card" style="box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);">
<div class="layui-card-body" style="text-align: center" id="tooltip-cpus">
加载中......
</div>
</div>
</div>
{% include 'system/common/footer.html' %} {% include 'system/common/footer.html' %}
<script> <script>
var systemInfo = null; // 存储获取到的系统信息对象
function memoryFormat(memory) {
if (memory >= 1024 ** 4 * 2) {
return (memory / 1024 ** 4).toFixed(2) + 'TB';
} else if (memory >= 1024 ** 3 * 2) {
return (memory / 1024 ** 3).toFixed(2) + 'GB';
} else if (memory >= 1024 ** 2 * 2) {
return (memory / 1024 ** 2).toFixed(2) + 'MB';
} else if (memory >= 1024 ** 1 * 2) {
return (memory / 1024 ** 1).toFixed(2) + 'KB';
} else {
return memory + 'B';
}
}
function diskinfo(index) {
data = systemInfo.disk_partitions_list[index];
layui.laytpl(`
<ul>
<li>分区类型: <%= d.fstype %></li>
<li>磁盘大小: <%= memoryFormat(d.total) %></li>
<li>空闲大小: <%= memoryFormat(d.free) %></li>
<li>已经使用: <%= memoryFormat(d.used) %> (<%= d.percent %>%)</li>
</ul>
`, {
open: '<%',
close: '%>'
}).render(data, function (string) {
layui.layer.alert(string);
});
}
function kill() {
var success = true;
layui.$.ajax({
url: "/system/monitor/kill",
success: function (res) {
layui.popup.failure(res.msg);
success = false;
},
complete: function (xhr) {
if (success) layui.popup.success("已发送关闭命令。");
}
});
}
layui.use(['layer', 'echarts', 'popup'], function () { layui.use(['layer', 'echarts', 'popup'], function () {
var $ = layui.jquery, var $ = layui.jquery,
echarts = layui.echarts; echarts = layui.echarts;
let popup = layui.popup; let popup = layui.popup;
let laytpl = layui.laytpl;
var echartsRecords = echarts.init(document.getElementById('echarts-records'), 'walden'); var echartsRecords = echarts.init(document.getElementById('echarts-records'), 'walden');
$('#host-info-card').hover(
function () {
$('#tooltip').addClass('show');
// 获取 #cpus_percent-card 的位置、尺寸和宽度
const card = $(this);
const cardOffset = card.offset(); // 元素相对于文档的偏移量
const cardHeight = card.outerHeight(); // 元素的高度
const cardWidth = card.outerWidth(); // 元素的宽度
// 设置悬浮提示框的宽度和位置
$('#tooltip').css({
top: cardOffset.top + cardHeight + 2, // 放在元素下方 5px
left: cardOffset.left - 5, // 与元素左对齐
width: cardWidth - 10, // 宽度与元素一致
});
},
function () {
$('#tooltip').removeClass('show');
}
);
$("body").on("click", "[data-url]", function () { $("body").on("click", "[data-url]", function () {
parent.layui.tab.addTabOnlyByElem("content", { parent.layui.tab.addTabOnlyByElem("content", {
id: $(this).attr("data-id"), id: $(this).attr("data-id"),
@@ -188,20 +254,6 @@
"#00CA69" "#00CA69"
]; ];
let echartData = [
{
name: "{{time_now}}",
cpu_percent: {{ cpus_percent }},
memory_percent: {{ memory_usage }}
},
];
var xAxisData = echartData.map(v => v.name);
// ["1", "2", "3", "4", "5", "6", "7", "8"]
var yAxisData1 = echartData.map(v => v.cpu_percent);
// [100, 138, 350, 173, 180, 150, 180, 230]
var yAxisData2 = echartData.map(v => v.memory_percent);
// [233, 233, 200, 180, 199, 233, 210, 180]
const hexToRgba = (hex, opacity) => { const hexToRgba = (hex, opacity) => {
let rgbaColor = ""; let rgbaColor = "";
let reg = /^#[\da-f]{6}$/i; let reg = /^#[\da-f]{6}$/i;
@@ -214,7 +266,9 @@
return rgbaColor; return rgbaColor;
} }
option = { let echartData = [];
var option = {
backgroundColor: bgColor, backgroundColor: bgColor,
color: color, color: color,
legend: { legend: {
@@ -223,25 +277,9 @@
}, },
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
formatter: function (params) {
let html = '';
params.forEach(v => {
html +=
`<div style="color: #666;font-size: 14px;line-height: 24px">
<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${color[v.componentIndex]};"></span>
${v.seriesName}.${v.name}
<span style="color:${color[v.componentIndex]};font-weight:700;font-size: 18px">${v.value}</span>
%`;
})
return html
},
extraCssText: 'background: #fff; border-radius: 0;box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);color: #333;',
axisPointer: { axisPointer: {
type: 'shadow', type: 'shadow',
shadowStyle: { shadowStyle: {
color: '#ffffff',
shadowColor: 'rgba(225,225,225,1)', shadowColor: 'rgba(225,225,225,1)',
shadowBlur: 5 shadowBlur: 5
} }
@@ -265,7 +303,7 @@
color: "#D9D9D9" color: "#D9D9D9"
} }
}, },
data: xAxisData data: null
}], }],
yAxis: [{ yAxis: [{
type: "value", type: "value",
@@ -329,7 +367,7 @@
shadowBlur: 10 shadowBlur: 10
} }
}, },
data: yAxisData1 data: null
}, { }, {
name: '内存', name: '内存',
type: "line", type: "line",
@@ -366,7 +404,7 @@
shadowBlur: 10 shadowBlur: 10
} }
}, },
data: yAxisData2 data: null
}] }]
}; };
@@ -376,17 +414,96 @@
echartsRecords.resize(); echartsRecords.resize();
}; };
setInterval(ajaxPolling, 1000 * 10); ajaxPolling();
setInterval(ajaxPolling, 1000 * 5);
function ajaxPolling() { function ajaxPolling() {
$.ajax({ $.ajax({
url: "/system/monitor/polling", url: "/system/monitor/polling",
success: function (data) { success: function (result) {
data = result.data;
systemInfo = data;
// 更新使用率等信息
document.querySelector("#cpu_count").innerText = data.cpu_count;
document.querySelector("#cpus_percent").innerText = data.cpus_percent + "%";
document.querySelector("#cpu_idle_percent").innerText = data.cpu_idle_percent + "%";
document.querySelector("#cpu_wait_percent").innerText = data.cpu_wait_percent + "%";
document.querySelector("#memory_used").innerText = memoryFormat(data.memory_used);
document.querySelector("#memory_total").innerText = memoryFormat(data.memory_total);
document.querySelector("#memory_free").innerText = memoryFormat(data.memory_free);
document.querySelector("#memory_usage").innerText = data.memory_usage + "%";
// 基本信息
document.querySelector("#hostname").innerText = data.basic_info.hostname;
document.querySelector("#system_version").innerText = data.basic_info.system_version;
document.querySelector("#boot_time").innerText = data.basic_info.boot_time;
document.querySelector("#up_time_format").innerText = data.basic_info.up_time_format;
document.querySelector("#python_version").innerText = data.basic_info.python_version;
// 更新每一个CPU使用率
laytpl(`
<ul>
<%# layui.each(d, function(index, data){ %>
<li>
<span class="layui-badge layui-bg-blue">CPU <%= data[0] %>: <%= data[1] %>%</span>
</li>
<%# }); %>
</ul>
`, {
open: '<%',
close: '%>'
}).render(data.cpu_percent_per_core, function (string) {
document.querySelector("#tooltip-cpus").innerHTML = string;
});
// 更新硬盘信息
laytpl(`
<fieldset class="layui-elem-field layui-field-title"></fieldset>
<%# layui.each(d, function(index, data){ %>
<div class="layui-row" style="display: flex; align-items: center;">
<div class="progress-ring" style="margin-right: 50px;">
<svg class="circle" width="120" height="120" viewBox="0 0 120 120">
<circle class="circle-bg" cx="60" cy="60" r="54"/>
<circle class="circle-progress" cx="60" cy="60" r="54" style="--progress: <%= data.percent %>"/>
</svg>
<div class="progress-text"><%= data.device %></div>
</div>
<div class="layui-col-space10" style="display: flex; align-items: center;">
<ul>
<li>分区类型: <%= data.fstype %></li>
<li>磁盘大小: <%= memoryFormat(data.total) %></li>
<li>空闲大小: <%= memoryFormat(data.free) %></li>
<li>已经使用: <%= memoryFormat(data.used) %> (<%= data.percent %>%)</li>
<li>
<a href="javascript:diskinfo(<%= index %>)" data-id="1"
class="layui-btn layui-btn-xs layui-btn-primary pear-reply">查看详情
</a>
</li>
</ul>
</div>
</div>
<fieldset class="layui-elem-field layui-field-title"></fieldset>
<%# }) %>
`, {
open: '<%',
close: '%>'
}).render(data.disk_partitions_list, function (string) {
document.querySelector("#disk-info-card").innerHTML = string;
});
// 更新图表
echartData.push({ echartData.push({
name: data.time_now, name: data.time_now,
cpu_percent: data.cups_percent, cpu_percent: data.cpus_percent,
memory_percent: data.memory_used memory_percent: data.memory_usage
}); });
if (echartData.length > 8) { if (echartData.length > 8) {
echartData.shift(); echartData.shift();
} }
@@ -403,7 +520,6 @@
}, },
error: function (xhr, type, errorThrown) { error: function (xhr, type, errorThrown) {
popup.failure("api错误");
} }
}) })