!27 发布 v1.1.0
发布 v1.1.0 功能: 新增服务监控功能 新增操作日志功能 新增导入功能 新增celery定时任务 新增消息通知功能 新增后端接口文档 新增docker-compose部署 新增支持重写用户模型 数据权限完善 登录日志优化 后端代码架构优化 文件管理功能完善 修复创建用户密码问题 其他细节优化pull/27/MERGE v1.1.0
						commit
						44a8134c01
					
				
							
								
								
									
										51
									
								
								README.md
								
								
								
								
							
							
						
						
									
										51
									
								
								README.md
								
								
								
								
							| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
# Django-Vue-Admin
 | 
			
		||||
 | 
			
		||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE) [](https://pypi.org/project/django-simpleui/#history) [](https://python.org/)  
 | 
			
		||||
[](https://gitee.com/liqianglog/django-vue-admin/blob/master/LICENSE)  [](https://python.org/)  [](https://docs.djangoproject.com/zh-hans/2.2/) [](https://nodejs.org/zh-cn/) [](https://gitee.com/liqianglog/django-vue-admin)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ Django-Vue-Admin 是一套全部开源的快速开发平台,毫无保留给个
 | 
			
		|||
* 后端采用Python语言Django框架。
 | 
			
		||||
* 权限认证使用Jwt,支持多终端认证系统。
 | 
			
		||||
* 支持加载动态权限菜单,多方式轻松权限控制。
 | 
			
		||||
* 特别鸣谢:<u>[Gin-Vue-Admin](https://www.gin-vue-admin.com/)</u>,[RuoYi](https://gitee.com/y_project/RuoYi-Vue) ,[Vue-Element-Admin](https://github.com/PanJiaChen/vue-element-admin),[eladmin-web](https://gitee.com/elunez/eladmin-web?_from=gitee_search)。
 | 
			
		||||
* 特别鸣谢:<u>[Gin-Vue-Admin](https://www.gin-vue-admin.com/)</u>,[RuoYi](https://gitee.com/y_project/RuoYi-Vue) ,[Vue-Element-Admin](https://github.com/PanJiaChen/vue-element-admin)。
 | 
			
		||||
 | 
			
		||||
## QQ群
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -47,12 +47,13 @@ github地址:[https://github.com/liqianglog/django-vue-admin](https://github.c
 | 
			
		|||
13.  在线用户:当前系统中活跃用户状态监控、用户强退功能。
 | 
			
		||||
14.  定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
 | 
			
		||||
15.  在线构建器:拖动表单元素生成相应的HTML代码。
 | 
			
		||||
16.  服务监控:进行可视化的服务器监控,CPU、内存、文件使用率等信息。
 | 
			
		||||
 | 
			
		||||
## 在线体验
 | 
			
		||||
 | 
			
		||||
演示地址:[https://demo.django-vue-admin.com/](https://demo.django-vue-admin.com/) 账号:admin 密码:123456
 | 
			
		||||
演示地址:[http://demo.django-vue-admin.com](http://demo.django-vue-admin.com) 账号:admin 密码:123456
 | 
			
		||||
 | 
			
		||||
文档地址:[https://django-vue-admin.com/](https://django-vue-admin.com/)
 | 
			
		||||
文档地址:[http://django-vue-admin.com](http://django-vue-admin.com)
 | 
			
		||||
 | 
			
		||||
## 前端
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -92,9 +93,8 @@ npm run build:prod
 | 
			
		|||
2. 在项目根目录中,复制 ./conf/env.example.py 文件为一份新的到 ./conf 文件夹下,并重命名为 env.py
 | 
			
		||||
 | 
			
		||||
3. 在 env.py 中配置数据库信息
 | 
			
		||||
	mysql数据库版本建议:5.7以上
 | 
			
		||||
	mysql数据库版本建议:8.0
 | 
			
		||||
	mysql数据库字符集:utf8mb4
 | 
			
		||||
	mysql数据库排序规则:utf8mb4_0900_ai_ci
 | 
			
		||||
	
 | 
			
		||||
4. 安装依赖环境
 | 
			
		||||
	pip3 install -r requirements.txt
 | 
			
		||||
| 
						 | 
				
			
			@ -138,31 +138,40 @@ exit
 | 
			
		|||
 | 
			
		||||
<table>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/155624_fc01f49e_5074988.jpeg" height="200" width="400"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/162526_68e8c4c5_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/707825ad3f29de74a8d6d02fbd73ad631ea.jpg"/></td>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/46be40cc6f01aa300eed53a19b5012bf484.jpg"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/163049_0a16b3b8_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/163157_628941bc_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/4284796d4cea240d181b8f2201813dda710.jpg"/></td>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/3ecfac87a049f7fe36abbcaafb2c40d36cf.jpg"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/163444_73d4a6ae_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/163456_c4ddcaf6_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
	<tr>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/71c2d48905221a09a728df4aff4160b8607.jpg"/></td>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/c14c1ee9a64a6a9c2c22f67d43198767dbe.jpg"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/163732_48cca279_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/163756_99176d5d_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
    </tr>	 
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/164149_b223657a_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/164226_58653572_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
	<tr>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/fdea1d8bb8625c27bf964176a2c8ebc6945.jpg"/></td>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/509d2708cfd762b6e6339364cac1cc1970c.jpg"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/164259_e06fbfe9_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/164330_6406c28f_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
	<tr>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/up-f1fd681cc9d295db74e85ad6d2fe4389454.png"/></td>
 | 
			
		||||
        <td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
 | 
			
		||||
    <tr>
 | 
			
		||||
   		 	<td><img src="https://images.gitee.com/uploads/images/2021/0505/164359_add984a1_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/181144_9665dae5_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
        <td><img src="https://images.gitee.com/uploads/images/2021/0505/181700_25edc19f_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
    <td><img src="https://images.gitee.com/uploads/images/2021/0505/181715_9305b7e8_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    <tr>
 | 
			
		||||
    <td><img src="https://images.gitee.com/uploads/images/2021/0505/181732_953b05e4_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
      <td><img src="https://images.gitee.com/uploads/images/2021/0505/182122_73bddac6_5074988.png" height="200" width="400"/></td>
 | 
			
		||||
    </tr>
 | 
			
		||||
</table>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,6 +8,8 @@ version: "3"
 | 
			
		|||
services:
 | 
			
		||||
  dvadmin-ui:
 | 
			
		||||
    container_name: dvadmin-ui
 | 
			
		||||
    ports:
 | 
			
		||||
      - "8080:8080"
 | 
			
		||||
    build:
 | 
			
		||||
      context: ./
 | 
			
		||||
      dockerfile: ./docker_env/vue-ui/Dockerfile
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,5 +12,6 @@ WORKDIR /dvadmin-backend
 | 
			
		|||
COPY ./dvadmin-backend/requirements.txt /
 | 
			
		||||
COPY ./dvadmin-backend/conf/env.example.py /dvadmin-backend/conf/env.py
 | 
			
		||||
RUN python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ -r /requirements.txt
 | 
			
		||||
RUN python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ uwsgi==2.0.19.1
 | 
			
		||||
CMD ["/dvadmin-backend/docker_start.sh"]
 | 
			
		||||
# ENTRYPOINT [ "uwsgi --ini /backend/azcrm/uwsgi.ini" ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -51,6 +51,7 @@ INSTALLED_APPS = [
 | 
			
		|||
    'apps.vadmin.op_drf',
 | 
			
		||||
    'apps.vadmin.system',
 | 
			
		||||
    'apps.vadmin.celery',
 | 
			
		||||
    'apps.vadmin.monitor',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
MIDDLEWARE = [
 | 
			
		||||
| 
						 | 
				
			
			@ -242,15 +243,17 @@ else:
 | 
			
		|||
 | 
			
		||||
# redis 缓存
 | 
			
		||||
REDIS_URL = f'redis://:{REDIS_PASSWORD if REDIS_PASSWORD else ""}@{os.getenv("REDIS_HOST") or REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}'
 | 
			
		||||
CACHES = {
 | 
			
		||||
    "default": {
 | 
			
		||||
        "BACKEND": "django_redis.cache.RedisCache",
 | 
			
		||||
        "LOCATION": REDIS_URL,
 | 
			
		||||
        "OPTIONS": {
 | 
			
		||||
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
# 是否启用redis
 | 
			
		||||
if locals().get("REDIS_ENABLE", True):
 | 
			
		||||
    CACHES = {
 | 
			
		||||
        "default": {
 | 
			
		||||
            "BACKEND": "django_redis.cache.RedisCache",
 | 
			
		||||
            "LOCATION": REDIS_URL,
 | 
			
		||||
            "OPTIONS": {
 | 
			
		||||
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
# ================================================= #
 | 
			
		||||
# ******************** JWT配置  ******************** #
 | 
			
		||||
# ================================================= #
 | 
			
		||||
| 
						 | 
				
			
			@ -328,3 +331,4 @@ CELERYBEAT_SCHEDULER = 'django_celery_beat.schedulers.DatabaseScheduler'  # Back
 | 
			
		|||
# ================================================= #
 | 
			
		||||
# 接口权限
 | 
			
		||||
INTERFACE_PERMISSION = locals().get("INTERFACE_PERMISSION", False)
 | 
			
		||||
DJANGO_CELERY_BEAT_TZ_AWARE = False
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -22,7 +22,7 @@ from django.urls import re_path, include
 | 
			
		|||
from django.views.static import serve
 | 
			
		||||
from rest_framework.views import APIView
 | 
			
		||||
 | 
			
		||||
from vadmin.utils.response import SuccessResponse
 | 
			
		||||
from apps.vadmin.utils.response import SuccessResponse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CaptchaRefresh(APIView):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,6 @@
 | 
			
		|||
from django.apps import AppConfig
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MonitorConfig(AppConfig):
 | 
			
		||||
    name = 'vadmin.monitor'
 | 
			
		||||
    verbose_name = "系统监控"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
import django_filters
 | 
			
		||||
 | 
			
		||||
from .models import Server, Monitor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServerFilter(django_filters.rest_framework.FilterSet):
 | 
			
		||||
    """
 | 
			
		||||
    服务器信息 简单过滤器
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Server
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MonitorFilter(django_filters.rest_framework.FilterSet):
 | 
			
		||||
    """
 | 
			
		||||
    服务器监控信息 简单过滤器
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Monitor
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
from ..models.monitor import Monitor
 | 
			
		||||
from ..models.server import Server
 | 
			
		||||
from ..models.sys_files import SysFiles
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
from django.db.models import CharField, ForeignKey, CASCADE
 | 
			
		||||
 | 
			
		||||
from ...op_drf.models import CoreModel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Monitor(CoreModel):
 | 
			
		||||
    cpu_num = CharField(max_length=8, verbose_name='CPU核数')
 | 
			
		||||
    cpu_sys = CharField(max_length=8, verbose_name='CPU已使用率')
 | 
			
		||||
    mem_num = CharField(max_length=32, verbose_name='内存总数(KB)')
 | 
			
		||||
    mem_sys = CharField(max_length=32, verbose_name='内存已使用大小(KB)')
 | 
			
		||||
    seconds = CharField(max_length=32, verbose_name='系统已运行时间')
 | 
			
		||||
    server = ForeignKey(to='monitor.Server', on_delete=CASCADE, verbose_name="关联服务器信息", db_constraint=False)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = '服务器监控信息'
 | 
			
		||||
        verbose_name_plural = verbose_name
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.server and self.server.name and self.server.ip}监控信息"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
from django.db import models
 | 
			
		||||
from django.db.models import CharField
 | 
			
		||||
 | 
			
		||||
from apps.vadmin.op_drf.fields import UpdateDateTimeField, CreateDateTimeField
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Server(models.Model):
 | 
			
		||||
    name = CharField(max_length=256, verbose_name='服务器名称', null=True, blank=True)
 | 
			
		||||
    ip = CharField(max_length=32, verbose_name="ip地址")
 | 
			
		||||
    os = CharField(max_length=32, verbose_name="操作系统")
 | 
			
		||||
    remark = CharField(max_length=256, verbose_name="备注", null=True, blank=True)
 | 
			
		||||
    update_datetime = UpdateDateTimeField()  # 修改时间
 | 
			
		||||
    create_datetime = CreateDateTimeField()  # 创建时间
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = '服务器信息'
 | 
			
		||||
        verbose_name_plural = verbose_name
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.name and self.ip}"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,19 @@
 | 
			
		|||
from django.db.models import CharField, ForeignKey, CASCADE
 | 
			
		||||
 | 
			
		||||
from ...op_drf.models import CoreModel
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SysFiles(CoreModel):
 | 
			
		||||
    dir_name = CharField(max_length=32, verbose_name='磁盘路径')
 | 
			
		||||
    sys_type_name = CharField(max_length=400, verbose_name='系统文件类型')
 | 
			
		||||
    type_name = CharField(max_length=32, verbose_name='盘符类型')
 | 
			
		||||
    total = CharField(max_length=32, verbose_name='磁盘总大小(KB)')
 | 
			
		||||
    disk_sys = CharField(max_length=32, verbose_name='已使用大小(KB)')
 | 
			
		||||
    monitor = ForeignKey(to='monitor.Monitor', on_delete=CASCADE, verbose_name="关联服务器监控信息", db_constraint=False)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = '系统磁盘'
 | 
			
		||||
        verbose_name_plural = verbose_name
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.creator and self.creator.name}"
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,40 @@
 | 
			
		|||
from .models import Server, Monitor
 | 
			
		||||
from ..op_drf.serializers import CustomModelSerializer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ================================================= #
 | 
			
		||||
# ************** 服务器信息 序列化器  ************** #
 | 
			
		||||
# ================================================= #
 | 
			
		||||
 | 
			
		||||
class ServerSerializer(CustomModelSerializer):
 | 
			
		||||
    """
 | 
			
		||||
    服务器信息 简单序列化器
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Server
 | 
			
		||||
        fields = ("id", "ip", "name", "os", "remark")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UpdateServerSerializer(CustomModelSerializer):
 | 
			
		||||
    """
 | 
			
		||||
    服务器信息 简单序列化器
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Server
 | 
			
		||||
        fields = ("name", "remark")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ================================================= #
 | 
			
		||||
# ************** 服务器监控信息 序列化器  ************** #
 | 
			
		||||
# ================================================= #
 | 
			
		||||
 | 
			
		||||
class MonitorSerializer(CustomModelSerializer):
 | 
			
		||||
    """
 | 
			
		||||
    服务器监控信息 简单序列化器
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Monitor
 | 
			
		||||
        fields = '__all__'
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,89 @@
 | 
			
		|||
import datetime
 | 
			
		||||
import logging
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
import psutil
 | 
			
		||||
 | 
			
		||||
from ..monitor.models import Server, Monitor, SysFiles
 | 
			
		||||
from ..op_drf.response import SuccessResponse
 | 
			
		||||
from ..system.models import ConfigSettings
 | 
			
		||||
from ..utils.decorators import BaseCeleryApp
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger(__name__)
 | 
			
		||||
from platform import platform
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getIP():
 | 
			
		||||
    """获取ipv4地址"""
 | 
			
		||||
    dic = psutil.net_if_addrs()
 | 
			
		||||
    ipv4_list = []
 | 
			
		||||
    for adapter in dic:
 | 
			
		||||
        snicList = dic[adapter]
 | 
			
		||||
        for snic in snicList:
 | 
			
		||||
            if snic.family.name == 'AF_INET':
 | 
			
		||||
                ipv4 = snic.address
 | 
			
		||||
                if ipv4 != '127.0.0.1':
 | 
			
		||||
                    ipv4_list.append(ipv4)
 | 
			
		||||
    if len(ipv4_list) >= 1:
 | 
			
		||||
        return ipv4_list[0]
 | 
			
		||||
    else:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@BaseCeleryApp(name='apps.vadmin.monitor.tasks.get_monitor_info', save_success_logs=False)
 | 
			
		||||
def get_monitor_info():
 | 
			
		||||
    """
 | 
			
		||||
    定时获取系统监控信息
 | 
			
		||||
    :return:
 | 
			
		||||
    """
 | 
			
		||||
    # 获取服务器
 | 
			
		||||
    ip = getIP()
 | 
			
		||||
    if not ip:
 | 
			
		||||
        logger.error("无法获取到IP")
 | 
			
		||||
        return
 | 
			
		||||
    server_obj, create = Server.objects.get_or_create(ip=ip)
 | 
			
		||||
    if create:
 | 
			
		||||
        server_obj.name = ip
 | 
			
		||||
        terse = ('terse' in sys.argv or '--terse' in sys.argv)
 | 
			
		||||
        aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
 | 
			
		||||
        server_obj.os = platform(aliased, terse)
 | 
			
		||||
        server_obj.save()
 | 
			
		||||
 | 
			
		||||
    # 获取CPU和内存信息
 | 
			
		||||
    mem = psutil.virtual_memory()
 | 
			
		||||
    monitor_obj = Monitor()
 | 
			
		||||
    monitor_obj.cpu_num = psutil.cpu_count()
 | 
			
		||||
    monitor_obj.cpu_sys = float(psutil.cpu_percent(0.1))
 | 
			
		||||
    monitor_obj.mem_num = int(mem.total / 1024)
 | 
			
		||||
    monitor_obj.mem_sys = int(mem.used / 1024)
 | 
			
		||||
    monitor_obj.seconds = time.strftime("%d天 %H 小时 %M 分 %S 秒", time.gmtime(int(time.time()) - int(psutil.boot_time())))
 | 
			
		||||
    monitor_obj.server = server_obj
 | 
			
		||||
    monitor_obj.save()
 | 
			
		||||
 | 
			
		||||
    # 保存磁盘信息
 | 
			
		||||
    for ele in psutil.disk_partitions():
 | 
			
		||||
        disk = psutil.disk_usage('/')
 | 
			
		||||
 | 
			
		||||
        sys_files_obj = SysFiles()
 | 
			
		||||
        sys_files_obj.dir_name = ele.mountpoint
 | 
			
		||||
        sys_files_obj.sys_type_name = ele.opts
 | 
			
		||||
        sys_files_obj.type_name = ele.fstype
 | 
			
		||||
        sys_files_obj.total = disk.total
 | 
			
		||||
        sys_files_obj.disk_sys = disk.used
 | 
			
		||||
        sys_files_obj.monitor = monitor_obj
 | 
			
		||||
        sys_files_obj.save()
 | 
			
		||||
 | 
			
		||||
    return SuccessResponse(msg="")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@BaseCeleryApp(name='apps.vadmin.monitor.tasks.clean_surplus_monitor_info')
 | 
			
		||||
def clean_surplus_monitor_info():
 | 
			
		||||
    """
 | 
			
		||||
    定时清理多余 系统监控信息
 | 
			
		||||
    :return:
 | 
			
		||||
    """
 | 
			
		||||
    config_settings_obj = ConfigSettings.objects.filter(configKey='sys.monitor.info.save_days').first()
 | 
			
		||||
    Monitor.objects.filter(
 | 
			
		||||
        update_datetime__lt=datetime.timedelta(days=int(config_settings_obj.configValue or 30))).delete()
 | 
			
		||||
    logger.info(f"成功清空{config_settings_obj.configValue}天前数据")
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
from django.test import TestCase
 | 
			
		||||
 | 
			
		||||
# Create your tests here.
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
from django.urls import re_path
 | 
			
		||||
from rest_framework.routers import DefaultRouter
 | 
			
		||||
 | 
			
		||||
from .views import ServerModelViewSet, MonitorModelViewSet
 | 
			
		||||
 | 
			
		||||
router = DefaultRouter()
 | 
			
		||||
router.register(r'server', ServerModelViewSet)
 | 
			
		||||
router.register(r'monitor', MonitorModelViewSet)
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    re_path('monitor/info/(?P<pk>.*)/', MonitorModelViewSet.as_view({'get': 'get_monitor_info'})),
 | 
			
		||||
    re_path('monitor/rate/(?P<pk>.*)/', MonitorModelViewSet.as_view({'get': 'get_rate_info'})),
 | 
			
		||||
    re_path('monitor/enabled/', MonitorModelViewSet.as_view({'get': 'enabled_monitor_info'})),
 | 
			
		||||
    re_path('monitor/clean/', MonitorModelViewSet.as_view({'get': 'clean_all'})),
 | 
			
		||||
]
 | 
			
		||||
urlpatterns += router.urls
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,184 @@
 | 
			
		|||
from django_celery_beat.models import PeriodicTask, IntervalSchedule, CrontabSchedule
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
 | 
			
		||||
from .filters import ServerFilter, MonitorFilter
 | 
			
		||||
from .models import Server, Monitor, SysFiles
 | 
			
		||||
from .serializers import ServerSerializer, MonitorSerializer, UpdateServerSerializer
 | 
			
		||||
from ..op_drf.response import SuccessResponse, ErrorResponse
 | 
			
		||||
from ..op_drf.viewsets import CustomModelViewSet
 | 
			
		||||
from ..permission.permissions import CommonPermission
 | 
			
		||||
from ..system.models import ConfigSettings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServerModelViewSet(CustomModelViewSet):
 | 
			
		||||
    """
 | 
			
		||||
    服务器信息 模型的CRUD视图
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Server.objects.all()
 | 
			
		||||
    serializer_class = ServerSerializer
 | 
			
		||||
    update_serializer_class = UpdateServerSerializer
 | 
			
		||||
    # extra_filter_backends = [DataLevelPermissionsFilter]
 | 
			
		||||
    filter_class = ServerFilter
 | 
			
		||||
    update_extra_permission_classes = (CommonPermission,)
 | 
			
		||||
    destroy_extra_permission_classes = (CommonPermission,)
 | 
			
		||||
    create_extra_permission_classes = (CommonPermission,)
 | 
			
		||||
    ordering = '-create_datetime'  # 默认排序
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MonitorModelViewSet(CustomModelViewSet):
 | 
			
		||||
    """
 | 
			
		||||
    服务器监控信息 模型的CRUD视图
 | 
			
		||||
    """
 | 
			
		||||
    queryset = Monitor.objects.all()
 | 
			
		||||
    serializer_class = MonitorSerializer
 | 
			
		||||
    # extra_filter_backends = [DataLevelPermissionsFilter]
 | 
			
		||||
    filter_class = MonitorFilter
 | 
			
		||||
    update_extra_permission_classes = (CommonPermission,)
 | 
			
		||||
    destroy_extra_permission_classes = (CommonPermission,)
 | 
			
		||||
    create_extra_permission_classes = (CommonPermission,)
 | 
			
		||||
    ordering = '-create_datetime'  # 默认排序
 | 
			
		||||
 | 
			
		||||
    def get_rate_info(self, request: Request, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        获取使用率 监控信息
 | 
			
		||||
        :param request:
 | 
			
		||||
        :param args:
 | 
			
		||||
        :param kwargs:
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
        pk = kwargs.get("pk")
 | 
			
		||||
        queryset = self.filter_queryset(self.get_queryset())
 | 
			
		||||
        queryset = queryset.filter(server__id=pk).order_by("create_datetime")
 | 
			
		||||
        # 间隔取值
 | 
			
		||||
        queryset_count = queryset.count()
 | 
			
		||||
        Interval_num = 1 if queryset_count < 200 else int(queryset_count / 100)
 | 
			
		||||
        queryset = queryset.values('cpu_sys', 'mem_num', 'mem_sys', 'create_datetime')[::Interval_num][:100]
 | 
			
		||||
        data = {
 | 
			
		||||
            "cpu": [],
 | 
			
		||||
            "memory": [],
 | 
			
		||||
            "datetime": [],
 | 
			
		||||
        }
 | 
			
		||||
        for ele in queryset:
 | 
			
		||||
            data["cpu"].append(round(float(ele.get('cpu_sys', 0)), 2))
 | 
			
		||||
            data["datetime"].append(ele.get('create_datetime').strftime('%Y-%m-%d %H:%M:%S'))
 | 
			
		||||
            data["memory"].append(round(float(ele.get('mem_num', 0)), 4) and round(float(ele.get('mem_sys', 0)) /
 | 
			
		||||
                                                                                   float(ele.get('mem_num', 0)) * 100,
 | 
			
		||||
                                                                                   2))
 | 
			
		||||
        return SuccessResponse(data=data)
 | 
			
		||||
 | 
			
		||||
    def get_monitor_info(self, request: Request, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        最新的服务器状态信息
 | 
			
		||||
        :param request:
 | 
			
		||||
        :param args:
 | 
			
		||||
        :param kwargs:
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
        pk = kwargs.get("pk")
 | 
			
		||||
        instance = Monitor.objects.filter(server__id=pk).order_by("-create_datetime").first()
 | 
			
		||||
        if not instance:
 | 
			
		||||
            return ErrorResponse(msg="未找到服务器信息id")
 | 
			
		||||
        serializer = self.get_serializer(instance)
 | 
			
		||||
        data = serializer.data
 | 
			
		||||
        return SuccessResponse(data={
 | 
			
		||||
            "cpu": {
 | 
			
		||||
                "total": int(data.get('cpu_num'), 0),
 | 
			
		||||
                "used": "",  # cpu核心 可不传,如指cpu当前主频,该值可以传
 | 
			
		||||
                "rate": float(data.get('cpu_sys', 0)) / 100,
 | 
			
		||||
                "unit": "核心",  # 默认单位 核心
 | 
			
		||||
            },
 | 
			
		||||
            "memory": {
 | 
			
		||||
                "total": int(int(data.get('mem_num', 0)) / 1024),
 | 
			
		||||
                "used": int(int(data.get('mem_sys', 0)) / 1024),
 | 
			
		||||
                "rate": int(data.get('mem_num', 0)) and round(int(data.get('mem_sys', 0)) /
 | 
			
		||||
                                                              int(data.get('mem_num', 0)), 4),
 | 
			
		||||
                "unit": "MB",  # 默认单位 MB
 | 
			
		||||
            },
 | 
			
		||||
            "disk": [{
 | 
			
		||||
                "dir_name": ele.get('dir_name'),
 | 
			
		||||
                "total": int(int(ele.get('total', 0)) / 1024 / 1024 / 1024),
 | 
			
		||||
                "used": int(int(ele.get('disk_sys', 0)) / 1024 / 1024 / 1024),
 | 
			
		||||
                "rate": int(ele.get('total', 0)) and round(int(ele.get('disk_sys', 0)) / int(ele.get('total', 0)),
 | 
			
		||||
                                                           4),
 | 
			
		||||
                "unit": "GB",  # 默认单位 GB
 | 
			
		||||
            } for ele in SysFiles.objects.filter(monitor=instance).values('dir_name', 'total', 'disk_sys')]
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    def enabled_monitor_info(self, request: Request, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        更新和获取监控信息
 | 
			
		||||
        :param request:
 | 
			
		||||
        :param args:
 | 
			
		||||
        :param kwargs:
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
        enabled = request.query_params.get('enabled', None)
 | 
			
		||||
        save_days = request.query_params.get('save_days', None)
 | 
			
		||||
        interval = request.query_params.get('interval', None)
 | 
			
		||||
        # 定时获取系统监控信息
 | 
			
		||||
        periodictask_obj = PeriodicTask.objects.filter(task='apps.vadmin.monitor.tasks.get_monitor_info').first()
 | 
			
		||||
        if not periodictask_obj:
 | 
			
		||||
            intervalschedule_obj, _ = IntervalSchedule.objects.get_or_create(every=5, period="seconds")
 | 
			
		||||
            periodictask_obj = PeriodicTask()
 | 
			
		||||
            periodictask_obj.task = "apps.vadmin.monitor.tasks.get_monitor_info"
 | 
			
		||||
            periodictask_obj.name = "定时获取系统监控信息"
 | 
			
		||||
            periodictask_obj.interval = intervalschedule_obj
 | 
			
		||||
            periodictask_obj.enabled = False
 | 
			
		||||
            periodictask_obj.save()
 | 
			
		||||
 | 
			
		||||
        # 定时清理多余 系统监控信息
 | 
			
		||||
        clean_task_obj = PeriodicTask.objects.filter(
 | 
			
		||||
            task='apps.vadmin.monitor.tasks.clean_surplus_monitor_info').first()
 | 
			
		||||
        if not clean_task_obj:
 | 
			
		||||
            crontab_obj, _ = CrontabSchedule.objects.get_or_create(day_of_month="*", day_of_week="*", hour="1",
 | 
			
		||||
                                                                   minute="0", month_of_year="*")
 | 
			
		||||
            clean_task_obj = PeriodicTask()
 | 
			
		||||
            clean_task_obj.task = "apps.vadmin.monitor.tasks.clean_surplus_monitor_info"
 | 
			
		||||
            clean_task_obj.name = "定时清理多余-系统监控信息"
 | 
			
		||||
            clean_task_obj.crontab = crontab_obj
 | 
			
		||||
            clean_task_obj.enabled = True
 | 
			
		||||
            clean_task_obj.save()
 | 
			
		||||
        # 配置添加
 | 
			
		||||
        config_obj = ConfigSettings.objects.filter(configKey='sys.monitor.info.save_days').first()
 | 
			
		||||
        if not config_obj:
 | 
			
		||||
            config_obj = ConfigSettings()
 | 
			
		||||
            config_obj.configKey = "sys.monitor.info.save_days"
 | 
			
		||||
            config_obj.configName = "定时清理多余系统监控信息"
 | 
			
		||||
            config_obj.configValue = "30"
 | 
			
		||||
            config_obj.configType = "Y"
 | 
			
		||||
            config_obj.status = "1"
 | 
			
		||||
            config_obj.remark = "定时清理多余-系统监控信息,默认30天"
 | 
			
		||||
            config_obj.save()
 | 
			
		||||
 | 
			
		||||
        if enabled:
 | 
			
		||||
            # 更新监控状态
 | 
			
		||||
            periodictask_obj.enabled = True if enabled == "1" else False
 | 
			
		||||
            periodictask_obj.save()
 | 
			
		||||
 | 
			
		||||
            # 更新 定时清理多余 系统监控信息 状态
 | 
			
		||||
            clean_task_obj.enabled = True if enabled == "1" else False
 | 
			
		||||
            clean_task_obj.save()
 | 
			
		||||
        # 更新保留天数
 | 
			
		||||
        if save_days and config_obj:
 | 
			
		||||
            config_obj.configValue = save_days
 | 
			
		||||
            config_obj.save()
 | 
			
		||||
        # 更新监控获取频率
 | 
			
		||||
        if interval:
 | 
			
		||||
            periodictask_obj.interval.every = interval
 | 
			
		||||
            periodictask_obj.interval.save()
 | 
			
		||||
        return SuccessResponse(data={
 | 
			
		||||
            "enabled": periodictask_obj.enabled,
 | 
			
		||||
            "interval": periodictask_obj.interval.every,
 | 
			
		||||
            "save_days": config_obj.configValue if config_obj else "30",
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
    def clean_all(self, request: Request, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        清空监控信息
 | 
			
		||||
        :param request:
 | 
			
		||||
        :param args:
 | 
			
		||||
        :param kwargs:
 | 
			
		||||
        :return:
 | 
			
		||||
        """
 | 
			
		||||
        self.get_queryset().delete()
 | 
			
		||||
        return SuccessResponse(msg="清空成功")
 | 
			
		||||
| 
						 | 
				
			
			@ -315,8 +315,9 @@ class ImportSerializerMixin:
 | 
			
		|||
        # 导出模板
 | 
			
		||||
        if request.method == 'GET':
 | 
			
		||||
            # 示例数据
 | 
			
		||||
            queryset = self.filter_queryset(self.get_queryset())
 | 
			
		||||
            return SuccessResponse(
 | 
			
		||||
                export_excel_save_model(request, self.import_field_data.values(), [], '导入用户数据模板.xls'))
 | 
			
		||||
                export_excel_save_model(request, self.import_field_data.values(), [], f'导入{get_verbose_name(queryset)}模板.xls'))
 | 
			
		||||
        updateSupport = request.data.get('updateSupport')
 | 
			
		||||
        # 从excel中组织对应的数据结构,然后使用序列化器保存
 | 
			
		||||
        data = excel_to_data(request.data.get('file_url'), self.import_field_data)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
from django.conf import settings
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.db.models import SET_NULL
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +25,7 @@ class CoreModel(models.Model):
 | 
			
		|||
    增加审计字段, 覆盖字段时, 字段名称请勿修改, 必须统一审计字段名称
 | 
			
		||||
    """
 | 
			
		||||
    description = DescriptionField()  # 描述
 | 
			
		||||
    creator = models.ForeignKey(to='permission.UserProfile', related_query_name='creator_query', null=True,
 | 
			
		||||
    creator = models.ForeignKey(to=settings.AUTH_USER_MODEL, related_query_name='creator_query', null=True,
 | 
			
		||||
                                verbose_name='创建者', on_delete=SET_NULL, db_constraint=False)  # 创建者
 | 
			
		||||
    modifier = ModifierCharField()  # 修改者
 | 
			
		||||
    dept_belong_id = models.CharField(max_length=64, verbose_name="数据归属部门", null=True, blank=True)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,10 @@
 | 
			
		|||
from django.utils.functional import cached_property
 | 
			
		||||
 | 
			
		||||
from rest_framework import serializers
 | 
			
		||||
from rest_framework.serializers import ModelSerializer
 | 
			
		||||
from rest_framework.fields import empty
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from rest_framework.serializers import ModelSerializer
 | 
			
		||||
from rest_framework.utils.serializer_helpers import BindingDict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomModelSerializer(ModelSerializer):
 | 
			
		||||
| 
						 | 
				
			
			@ -30,6 +33,8 @@ class CustomModelSerializer(ModelSerializer):
 | 
			
		|||
        return super().save(**kwargs)
 | 
			
		||||
 | 
			
		||||
    def create(self, validated_data):
 | 
			
		||||
        if self.context.get('request'):
 | 
			
		||||
            self.request = self.context.get('request')
 | 
			
		||||
        if self.request:
 | 
			
		||||
            username = self.get_request_username()
 | 
			
		||||
            if self.modifier_field_name in self.fields.fields:
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +42,7 @@ class CustomModelSerializer(ModelSerializer):
 | 
			
		|||
            if self.creator_field_name in self.fields.fields:
 | 
			
		||||
                validated_data[self.creator_field_name] = self.request.user
 | 
			
		||||
            if self.dept_belong_id_field_name in self.fields.fields:
 | 
			
		||||
                validated_data[self.dept_belong_id_field_name] = getattr(self.request.user,'dept_id',None)
 | 
			
		||||
                validated_data[self.dept_belong_id_field_name] = getattr(self.request.user, 'dept_id', None)
 | 
			
		||||
        return super().create(validated_data)
 | 
			
		||||
 | 
			
		||||
    def update(self, instance, validated_data):
 | 
			
		||||
| 
						 | 
				
			
			@ -51,11 +56,11 @@ class CustomModelSerializer(ModelSerializer):
 | 
			
		|||
            return getattr(self.request.user, 'username', None)
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    @cached_property
 | 
			
		||||
    def fields(self):
 | 
			
		||||
        fields = super().fields
 | 
			
		||||
        fields = BindingDict(self)
 | 
			
		||||
        for key, value in self.get_fields().items():
 | 
			
		||||
            fields[key] = value
 | 
			
		||||
 | 
			
		||||
        if not hasattr(self, '_context'):
 | 
			
		||||
            return fields
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,28 @@
 | 
			
		|||
"""
 | 
			
		||||
重写校验器返回字段
 | 
			
		||||
"""
 | 
			
		||||
from rest_framework.validators import UniqueValidator, qs_exists
 | 
			
		||||
 | 
			
		||||
from vadmin.utils.exceptions import APIException
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CustomUniqueValidator(UniqueValidator):
 | 
			
		||||
    """
 | 
			
		||||
    继承,重写必填字段的验证器结果,防止字段暴露
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __call__(self, value, serializer_field):
 | 
			
		||||
        # Determine the underlying model field name. This may not be the
 | 
			
		||||
        # same as the serializer field name if `source=<>` is set.
 | 
			
		||||
        field_name = serializer_field.source_attrs[-1]
 | 
			
		||||
        # Determine the existing instance, if this is an update operation.
 | 
			
		||||
        instance = getattr(serializer_field.parent, 'instance', None)
 | 
			
		||||
 | 
			
		||||
        queryset = self.queryset
 | 
			
		||||
        queryset = self.filter_queryset(value, queryset, field_name)
 | 
			
		||||
        queryset = self.exclude_current_instance(queryset, instance)
 | 
			
		||||
        if qs_exists(queryset):
 | 
			
		||||
            raise APIException(message=self.message)
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return super().__repr__()
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,11 @@
 | 
			
		|||
import django_filters
 | 
			
		||||
from django.contrib.auth import get_user_model
 | 
			
		||||
 | 
			
		||||
from ..permission.models import Menu, Dept, Post, Role, UserProfile
 | 
			
		||||
from ..permission.models import Menu, Dept, Post, Role
 | 
			
		||||
from ..utils.model_util import get_dept
 | 
			
		||||
 | 
			
		||||
UserProfile = get_user_model()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MenuFilter(django_filters.rest_framework.FilterSet):
 | 
			
		||||
    """
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
import logging
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.core.management.base import BaseCommand
 | 
			
		||||
from django.db import connection
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -75,6 +76,7 @@ class Command(BaseCommand):
 | 
			
		|||
        parser.add_argument('-N', nargs='*')
 | 
			
		||||
 | 
			
		||||
    def handle(self, *args, **options):
 | 
			
		||||
        user_name = "_".join(settings.AUTH_USER_MODEL.lower().split("."))
 | 
			
		||||
        init_dict = {
 | 
			
		||||
            'system_dictdata': [os.path.join('system', 'system_dictdata.sql'), '字典管理', 'system_dictdata'],
 | 
			
		||||
            'system_dictdetails': [os.path.join('system', 'system_dictdetails.sql'), '字典详情', 'system_dictdetails'],
 | 
			
		||||
| 
						 | 
				
			
			@ -86,8 +88,7 @@ class Command(BaseCommand):
 | 
			
		|||
            'permission_role': [os.path.join('permission', 'permission_role.sql'), '角色管理',
 | 
			
		||||
                                ','.join(['permission_role', 'permission_role_dept', 'permission_role_menu'])],
 | 
			
		||||
            'permission_userprofile': [os.path.join('permission', 'permission_userprofile.sql'), '用户管理', ','.join(
 | 
			
		||||
                ['permission_userprofile_groups', 'permission_userprofile', 'permission_userprofile_role',
 | 
			
		||||
                 'permission_userprofile_post'])]
 | 
			
		||||
                [f'{user_name}_groups', f'{user_name}', f'{user_name}_role', f'{user_name}_post'])]
 | 
			
		||||
        }
 | 
			
		||||
        init_name = options.get('init_name')
 | 
			
		||||
        is_yes = None
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ class Dept(CoreModel):
 | 
			
		|||
    phone = CharField(max_length=32, verbose_name="联系电话", null=True, blank=True)
 | 
			
		||||
    email = CharField(max_length=32, verbose_name="邮箱", null=True, blank=True)
 | 
			
		||||
    status = CharField(max_length=8, verbose_name="部门状态", null=True, blank=True)
 | 
			
		||||
    parentId = ForeignKey(to='Dept', on_delete=CASCADE, default=False, verbose_name="上级部门",
 | 
			
		||||
    parentId = ForeignKey(to='permission.Dept', on_delete=CASCADE, default=False, verbose_name="上级部门",
 | 
			
		||||
                          db_constraint=False, null=True, blank=True)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,8 +18,8 @@ class Role(CoreModel):
 | 
			
		|||
    admin = BooleanField(default=False, verbose_name="是否为admin")
 | 
			
		||||
    dataScope = CharField(max_length=8,default='1', choices=DATASCOPE_CHOICES, verbose_name="权限范围",)
 | 
			
		||||
    remark = TextField(verbose_name="备注", help_text="备注", null=True, blank=True)
 | 
			
		||||
    dept = ManyToManyField(to='Dept', verbose_name='数据权限-关联部门', db_constraint=False)
 | 
			
		||||
    menu = ManyToManyField(to='Menu', verbose_name='关联菜单权限', db_constraint=False)
 | 
			
		||||
    dept = ManyToManyField(to='permission.Dept', verbose_name='数据权限-关联部门', db_constraint=False)
 | 
			
		||||
    menu = ManyToManyField(to='permission.Menu', verbose_name='关联菜单权限', db_constraint=False)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = '角色管理'
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,6 @@
 | 
			
		|||
from uuid import uuid4
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.contrib.auth.models import UserManager, AbstractUser
 | 
			
		||||
from django.core.cache import cache
 | 
			
		||||
from django.db.models import IntegerField, ForeignKey, CharField, TextField, ManyToManyField, CASCADE
 | 
			
		||||
| 
						 | 
				
			
			@ -22,9 +23,9 @@ class UserProfile(AbstractUser, CoreModel):
 | 
			
		|||
    gender = CharField(max_length=8, verbose_name="性别", null=True, blank=True)
 | 
			
		||||
    remark = TextField(verbose_name="备注", null=True)
 | 
			
		||||
    user_type = IntegerField(default=0, verbose_name="用户类型")
 | 
			
		||||
    post = ManyToManyField(to='Post', verbose_name='关联岗位', db_constraint=False)
 | 
			
		||||
    role = ManyToManyField(to='Role', verbose_name='关联角色', db_constraint=False)
 | 
			
		||||
    dept = ForeignKey(to='Dept', verbose_name='归属部门', on_delete=CASCADE, db_constraint=False, null=True, blank=True)
 | 
			
		||||
    post = ManyToManyField(to='permission.Post', verbose_name='关联岗位', db_constraint=False)
 | 
			
		||||
    role = ManyToManyField(to='permission.Role', verbose_name='关联角色', db_constraint=False)
 | 
			
		||||
    dept = ForeignKey(to='permission.Dept', verbose_name='归属部门', on_delete=CASCADE, db_constraint=False, null=True, blank=True)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def get_user_interface_dict(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +52,7 @@ class UserProfile(AbstractUser, CoreModel):
 | 
			
		|||
        return cache.delete(f'permission_interface_dict_{self.username}')
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        abstract = settings.AUTH_USER_MODEL != 'permission.UserProfile'
 | 
			
		||||
        verbose_name = '用户管理'
 | 
			
		||||
        verbose_name_plural = verbose_name
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,10 +1,13 @@
 | 
			
		|||
from django.contrib.auth import get_user_model
 | 
			
		||||
from rest_framework import serializers
 | 
			
		||||
from rest_framework.validators import UniqueValidator
 | 
			
		||||
 | 
			
		||||
from ..op_drf.serializers import CustomModelSerializer
 | 
			
		||||
from ..permission.models import Menu, Dept, Post, Role, UserProfile
 | 
			
		||||
from ..op_drf.validator import CustomUniqueValidator
 | 
			
		||||
from ..permission.models import Menu, Dept, Post, Role
 | 
			
		||||
from ..system.models import MessagePush
 | 
			
		||||
 | 
			
		||||
UserProfile = get_user_model()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ================================================= #
 | 
			
		||||
# ************** 菜单管理 序列化器  ************** #
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +260,8 @@ class UserProfileCreateUpdateSerializer(CustomModelSerializer):
 | 
			
		|||
    post = PostSerializer(many=True, read_only=True)
 | 
			
		||||
    role = RoleSerializer(many=True, read_only=True)
 | 
			
		||||
    username = serializers.CharField(required=True, max_length=150,
 | 
			
		||||
                                     validators=[UniqueValidator(queryset=UserProfile.objects.all(), message="用戶已存在")],
 | 
			
		||||
                                     validators=[
 | 
			
		||||
                                         CustomUniqueValidator(queryset=UserProfile.objects.all(), message="用戶已存在")],
 | 
			
		||||
                                     error_messages={
 | 
			
		||||
                                         "blank": "请输入用户名称",
 | 
			
		||||
                                         "required": "用户名称不能为空",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
from django.contrib.auth import authenticate
 | 
			
		||||
from django.contrib.auth import authenticate, get_user_model
 | 
			
		||||
from rest_framework.request import Request
 | 
			
		||||
from rest_framework.views import APIView
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6,7 +6,7 @@ from .permissions import CommonPermission, DeptDestroyPermission
 | 
			
		|||
from ..op_drf.filters import DataLevelPermissionsFilter
 | 
			
		||||
from ..op_drf.viewsets import CustomModelViewSet
 | 
			
		||||
from ..permission.filters import MenuFilter, DeptFilter, PostFilter, RoleFilter, UserProfileFilter
 | 
			
		||||
from ..permission.models import Role, Menu, Dept, Post, UserProfile
 | 
			
		||||
from ..permission.models import Role, Menu, Dept, Post
 | 
			
		||||
from ..permission.serializers import UserProfileSerializer, MenuSerializer, RoleSerializer, \
 | 
			
		||||
    MenuCreateUpdateSerializer, DeptSerializer, DeptCreateUpdateSerializer, PostSerializer, PostCreateUpdateSerializer, \
 | 
			
		||||
    RoleCreateUpdateSerializer, DeptTreeSerializer, MenuTreeSerializer, UserProfileCreateUpdateSerializer, \
 | 
			
		||||
| 
						 | 
				
			
			@ -15,6 +15,8 @@ from ..permission.serializers import UserProfileSerializer, MenuSerializer, Role
 | 
			
		|||
from ..system.models import DictDetails
 | 
			
		||||
from ..utils.response import SuccessResponse, ErrorResponse
 | 
			
		||||
 | 
			
		||||
UserProfile = get_user_model()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GetUserProfileView(APIView):
 | 
			
		||||
    """
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,7 @@
 | 
			
		|||
import os
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getSql(filename):
 | 
			
		||||
    """
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +11,10 @@ def getSql(filename):
 | 
			
		|||
    """
 | 
			
		||||
    abspath = os.path.abspath(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))
 | 
			
		||||
    pwd = os.path.join(abspath, 'scripts', filename)
 | 
			
		||||
    with open(pwd,'rb') as fp:
 | 
			
		||||
    with open(pwd, 'rb') as fp:
 | 
			
		||||
        content = fp.read().decode('utf8')
 | 
			
		||||
        if filename == "permission/permission_userprofile.sql":
 | 
			
		||||
            user_name = "_".join(settings.AUTH_USER_MODEL.lower().split("."))
 | 
			
		||||
            content = content.replace("permission_userprofile", user_name). \
 | 
			
		||||
                replace("userprofile", settings.AUTH_USER_MODEL.lower().split(".")[-1])
 | 
			
		||||
    return [ele for ele in content.split('\n') if not ele.startswith('--') and ele.strip(' ')]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -121,3 +121,9 @@ INSERT INTO `permission_menu` (id, description, modifier, update_datetime, creat
 | 
			
		|||
INSERT INTO `permission_menu` (id, description, modifier, update_datetime, create_datetime, menuType, icon, name, orderNum, isFrame, web_path, component_path, interface_path, interface_method, perms, status, visible, isCache, creator_id, parentId_id, dept_belong_id) VALUES (88, '', 'admin', '2021-03-21 23:33:30.888568', '2021-03-21 23:33:30.888593', '2', NULL, '登录日志清空', 4, '1', NULL, NULL, '/admin/system/logininfor/clean/', 'DELETE', 'admin:system:logininfor:clean:delete', '1', '1', '1', 1, 62, '1');
 | 
			
		||||
INSERT INTO `permission_menu` (id, description, modifier, update_datetime, create_datetime, menuType, icon, name, orderNum, isFrame, web_path, component_path, interface_path, interface_method, perms, status, visible, isCache, creator_id, parentId_id, dept_belong_id) VALUES (90, '', 'admin', '2021-03-26 00:44:00.756139', '2021-03-26 00:43:14.390228', '2', NULL, '定时日志批量删除', 3, '1', NULL, NULL, '/admin/system/celery_log/{id}/', 'DELETE', 'admin:system:celery_log:{id}:delete', '1', '1', '1', 1, 79, '8');
 | 
			
		||||
INSERT INTO `permission_menu` (id, description, modifier, update_datetime, create_datetime, menuType, icon, name, orderNum, isFrame, web_path, component_path, interface_path, interface_method, perms, status, visible, isCache, creator_id, parentId_id, dept_belong_id) VALUES (91, '', 'admin', '2021-03-26 00:44:36.135658', '2021-03-26 00:44:36.135679', '2', NULL, '定时日志清空', 4, '1', NULL, NULL, '/admin/system/celery_log/clean/', 'DELETE', 'admin:system:celery_log:clean:delete', '1', '1', '1', 1, 79, '8');
 | 
			
		||||
INSERT INTO `permission_menu` (id, description, modifier, update_datetime, create_datetime, menuType, icon, name, orderNum, isFrame, web_path, component_path, interface_path, interface_method, perms, status, visible, isCache, creator_id, parentId_id, dept_belong_id) VALUES (92, '', 'admin', '2021-04-27 23:49:59.036636', '2021-04-27 23:44:54.512207', '1', 'server', '服务监控', 3, '1', '/monitor/server', 'vadmin/monitor/server/index', NULL, 'GET', NULL, '1', '1', '1', 1, 66, '1');
 | 
			
		||||
INSERT INTO `permission_menu` (id, description, modifier, update_datetime, create_datetime, menuType, icon, name, orderNum, isFrame, web_path, component_path, interface_path, interface_method, perms, status, visible, isCache, creator_id, parentId_id, dept_belong_id) VALUES (93, '', 'admin', '2021-04-27 23:57:46.633022', '2021-04-27 23:49:28.569029', '2', NULL, '服务监控查询', 1, '1', NULL, NULL, '/admin/monitor/server/', 'GET', 'admin:monitor:server:get', '1', '1', '1', 1, 92, '1');
 | 
			
		||||
INSERT INTO `permission_menu` (id, description, modifier, update_datetime, create_datetime, menuType, icon, name, orderNum, isFrame, web_path, component_path, interface_path, interface_method, perms, status, visible, isCache, creator_id, parentId_id, dept_belong_id) VALUES (94, '', 'admin', '2021-04-27 23:58:44.705142', '2021-04-27 23:58:13.384483', '2', NULL, '修改服务器信息', 2, '1', NULL, NULL, '/admin/monitor/server/{id}/', 'PUT', 'admin:monitor:server:{id}:put', '1', '1', '1', 1, 92, '1');
 | 
			
		||||
INSERT INTO `permission_menu` (id, description, modifier, update_datetime, create_datetime, menuType, icon, name, orderNum, isFrame, web_path, component_path, interface_path, interface_method, perms, status, visible, isCache, creator_id, parentId_id, dept_belong_id) VALUES (95, '', 'admin', '2021-04-27 23:59:29.530633', '2021-04-27 23:59:07.744938', '2', NULL, '修改监控信息', 3, '1', NULL, NULL, '/admin/monitor/monitor/enabled/', 'GET', 'admin:monitor:monitor:enabled:get', '1', '1', '1', 1, 92, '1');
 | 
			
		||||
INSERT INTO `permission_menu` (id, description, modifier, update_datetime, create_datetime, menuType, icon, name, orderNum, isFrame, web_path, component_path, interface_path, interface_method, perms, status, visible, isCache, creator_id, parentId_id, dept_belong_id) VALUES (96, '', 'admin', '2021-04-28 00:01:15.071889', '2021-04-27 23:59:48.612905', '2', NULL, '清空监控记录', 4, '1', NULL, NULL, '/admin/monitor/monitor/clean/', 'GET', 'admin:monitor:monitor:clean:get', '1', '1', '1', 1, 92, '1');
 | 
			
		||||
INSERT INTO `permission_menu` (id, description, modifier, update_datetime, create_datetime, menuType, icon, name, orderNum, isFrame, web_path, component_path, interface_path, interface_method, perms, status, visible, isCache, creator_id, parentId_id, dept_belong_id) VALUES (97, '', 'admin', '2021-05-02 19:12:06.813143', '2021-05-02 18:58:06.260280', '0', 'dashboard', '首页', 0, '1', '/index', 'Layout/index', NULL, 'GET', NULL, '1', '1', '1', 1, NULL, '1');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ class DictDetails(CoreModel):
 | 
			
		|||
    is_default = BooleanField(verbose_name="是否默认", default=False)
 | 
			
		||||
    status = CharField(max_length=2, verbose_name="字典状态")
 | 
			
		||||
    sort = CharField(max_length=256, verbose_name="字典排序")
 | 
			
		||||
    dict_data = ForeignKey(to='DictData', on_delete=CASCADE, verbose_name="关联字典", db_constraint=False)
 | 
			
		||||
    dict_data = ForeignKey(to='system.DictData', on_delete=CASCADE, verbose_name="关联字典", db_constraint=False)
 | 
			
		||||
    remark = CharField(max_length=256, verbose_name="备注", null=True, blank=True)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,9 @@
 | 
			
		|||
from django.conf import settings
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.db.models import *
 | 
			
		||||
 | 
			
		||||
from ...op_drf.fields import UpdateDateTimeField, CreateDateTimeField
 | 
			
		||||
from ...op_drf.models import CoreModel
 | 
			
		||||
from ...permission.models import UserProfile
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
消息通知模型
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ class MessagePush(CoreModel):
 | 
			
		|||
    is_reviewed = BooleanField(default=True, verbose_name="是否审核")
 | 
			
		||||
    status = CharField(max_length=8, verbose_name="通知状态")
 | 
			
		||||
    to_path = CharField(max_length=256, verbose_name="跳转路径", null=True, blank=True, )
 | 
			
		||||
    user = ManyToManyField(to="permission.UserProfile",
 | 
			
		||||
    user = ManyToManyField(to=settings.AUTH_USER_MODEL,
 | 
			
		||||
                           related_name="user", related_query_name="user_query", through='MessagePushUser',
 | 
			
		||||
                           through_fields=('message_push', 'user'))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +34,7 @@ class MessagePushUser(models.Model):
 | 
			
		|||
                              related_name="messagepushuser_message_push",
 | 
			
		||||
                              verbose_name='消息通知', help_text='消息通知')
 | 
			
		||||
 | 
			
		||||
    user = ForeignKey(UserProfile, on_delete=CASCADE, db_constraint=False,
 | 
			
		||||
    user = ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=CASCADE, db_constraint=False,
 | 
			
		||||
                      related_name="messagepushuser_user",
 | 
			
		||||
                      verbose_name='用户', help_text='用户')
 | 
			
		||||
    is_read = BooleanField(default=False, verbose_name="是否已读")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -302,4 +302,4 @@ class ExportCeleryLogSerializer(CustomModelSerializer):
 | 
			
		|||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = CeleryLog
 | 
			
		||||
        fields = ('name', 'kwargs', 'seconds', 'state', 'result', 'creator_name')
 | 
			
		||||
        fields = ('name', 'kwargs', 'seconds', 'status', 'result', 'creator_name')
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,5 +54,6 @@ urlpatterns = [
 | 
			
		|||
    re_path(r'^permission/', include('apps.vadmin.permission.urls')),
 | 
			
		||||
    re_path(r'^system/', include('apps.vadmin.system.urls')),
 | 
			
		||||
    re_path(r'^celery/', include('apps.vadmin.celery.urls')),
 | 
			
		||||
    re_path(r'^monitor/', include('apps.vadmin.monitor.urls')),
 | 
			
		||||
 | 
			
		||||
]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,13 @@ def get_cache(alias=None):
 | 
			
		|||
logger = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def BaseCeleryApp(name):
 | 
			
		||||
def BaseCeleryApp(name, save_success_logs=True):
 | 
			
		||||
    """
 | 
			
		||||
    celery 保存日志基础类
 | 
			
		||||
    :param name: celery任务名字
 | 
			
		||||
    :param save_success_logs: 是否保存成功的日志(适用于频率高的celery任务,成功不需要保存日志,则传False)
 | 
			
		||||
    :return:
 | 
			
		||||
    """
 | 
			
		||||
    def wraps(func):
 | 
			
		||||
        @app.task(name=name)
 | 
			
		||||
        @functools.wraps(func)
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +46,8 @@ def BaseCeleryApp(name):
 | 
			
		|||
            res = None
 | 
			
		||||
            try:
 | 
			
		||||
                res = func(*args, **kwargs)
 | 
			
		||||
                if not save_success_logs:
 | 
			
		||||
                    return res
 | 
			
		||||
                obj.result = str(res)
 | 
			
		||||
                obj.status = True
 | 
			
		||||
            except Exception as exc:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,9 @@ DATABASE_NAME = "django-vue-admin"
 | 
			
		|||
# ================================================= #
 | 
			
		||||
# ************** redis 数据库配置  ************** #
 | 
			
		||||
# ================================================= #
 | 
			
		||||
# 是否启用Redis缓存
 | 
			
		||||
# 注:不使用redis则无法使用celery
 | 
			
		||||
REDIS_ENABLE = True
 | 
			
		||||
REDIS_DB = 1
 | 
			
		||||
REDIS_HOST = '127.0.0.1'
 | 
			
		||||
REDIS_PORT = 6379
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,7 +2,6 @@
 | 
			
		|||
 | 
			
		||||
cd /dvadmin-backend
 | 
			
		||||
cp -rf ./conf/env.example.py ./conf/env.py
 | 
			
		||||
python3 -m pip install -i https://mirrors.aliyun.com/pypi/simple/ uwsgi==2.0.19.1
 | 
			
		||||
python ./manage.py makemigrations
 | 
			
		||||
python ./manage.py migrate
 | 
			
		||||
#python ./manage.py initialization
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "ruoyi",
 | 
			
		||||
  "version": "3.3.0",
 | 
			
		||||
  "name": "dvadmin",
 | 
			
		||||
  "version": "1.1.0",
 | 
			
		||||
  "description": "dvAdmin管理系统",
 | 
			
		||||
  "author": "dvAdmin",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
| 
						 | 
				
			
			@ -41,14 +41,16 @@
 | 
			
		|||
    "axios": "0.21.0",
 | 
			
		||||
    "clipboard": "2.0.6",
 | 
			
		||||
    "core-js": "3.8.1",
 | 
			
		||||
    "echarts": "4.9.0",
 | 
			
		||||
    "echarts": "^4.9.0",
 | 
			
		||||
    "element-ui": "2.15.0",
 | 
			
		||||
    "eslint-loader": "^4.0.2",
 | 
			
		||||
    "file-saver": "2.0.4",
 | 
			
		||||
    "fuse.js": "6.4.3",
 | 
			
		||||
    "highlight.js": "9.18.5",
 | 
			
		||||
    "js-beautify": "1.13.0",
 | 
			
		||||
    "js-cookie": "2.2.1",
 | 
			
		||||
    "jsencrypt": "3.0.0-rc.1",
 | 
			
		||||
    "lodash": "^4.17.21",
 | 
			
		||||
    "moment": "^2.29.1",
 | 
			
		||||
    "nprogress": "0.2.0",
 | 
			
		||||
    "quill": "1.3.7",
 | 
			
		||||
| 
						 | 
				
			
			@ -58,6 +60,7 @@
 | 
			
		|||
    "vue-count-to": "1.0.13",
 | 
			
		||||
    "vue-cropper": "0.5.5",
 | 
			
		||||
    "vue-router": "3.4.9",
 | 
			
		||||
    "vue-types": "^2.0.3",
 | 
			
		||||
    "vuedraggable": "2.24.3",
 | 
			
		||||
    "vuex": "3.6.0"
 | 
			
		||||
  },
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +68,7 @@
 | 
			
		|||
    "@vue/cli-plugin-babel": "4.4.6",
 | 
			
		||||
    "@vue/cli-plugin-eslint": "4.4.6",
 | 
			
		||||
    "@vue/cli-service": "4.4.6",
 | 
			
		||||
    "@vue/composition-api": "^1.0.0-rc.6",
 | 
			
		||||
    "babel-eslint": "10.1.0",
 | 
			
		||||
    "chalk": "4.1.0",
 | 
			
		||||
    "connect": "3.6.6",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import request from '@/utils/request'
 | 
			
		|||
// 获取路由
 | 
			
		||||
export const getRouters = () => {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: '/admin/getRouters',
 | 
			
		||||
    url: '/admin/getRouters/',
 | 
			
		||||
    method: 'get'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import request from '@/utils/request'
 | 
			
		|||
// 查询在线用户列表
 | 
			
		||||
export function list(query) {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: '/monitor/online/list',
 | 
			
		||||
    url: '/admin/monitor/online/list',
 | 
			
		||||
    method: 'get',
 | 
			
		||||
    params: query
 | 
			
		||||
  })
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ export function list(query) {
 | 
			
		|||
// 强退用户
 | 
			
		||||
export function forceLogout(tokenId) {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: '/monitor/online/' + tokenId,
 | 
			
		||||
    url: '/admin/monitor/online/' + tokenId,
 | 
			
		||||
    method: 'delete'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,65 @@
 | 
			
		|||
import request from '@/utils/request'
 | 
			
		||||
 | 
			
		||||
// 查询服务器详细
 | 
			
		||||
export function getServer() {
 | 
			
		||||
// 查询服务器信息详细
 | 
			
		||||
export function getServerList(params) {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: '/monitor/server',
 | 
			
		||||
    url: 'admin/monitor/server/',
 | 
			
		||||
    params,
 | 
			
		||||
    method: 'get'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 修改服务器信息
 | 
			
		||||
export function updateServerInfo(id, data) {
 | 
			
		||||
  let {name, remark} = data;
 | 
			
		||||
  return request({
 | 
			
		||||
    url: `admin/monitor/server/${id}/`,
 | 
			
		||||
    data: {
 | 
			
		||||
      name,
 | 
			
		||||
      remark
 | 
			
		||||
    },
 | 
			
		||||
    method: 'PUT'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取监控配置信息
 | 
			
		||||
export function getMonitorStatusInfo() {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: 'admin/monitor/monitor/enabled/',
 | 
			
		||||
    method: 'get'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 更新监控配置信息
 | 
			
		||||
export function updateMonitorStatusInfo(params) {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: 'admin/monitor/monitor/enabled/',
 | 
			
		||||
    params,
 | 
			
		||||
    method: 'get'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 清空记录
 | 
			
		||||
export function cleanMonitorLog() {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: 'admin/monitor/monitor/clean/',
 | 
			
		||||
    method: 'delete'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取监控记录
 | 
			
		||||
export function getMonitorLogs(id, params) {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: `admin/monitor/monitor/rate/${id}/`,
 | 
			
		||||
    params,
 | 
			
		||||
    method: 'get'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取服务器最新监控日志信息
 | 
			
		||||
export function getServerLatestLog(id) {
 | 
			
		||||
  return request({
 | 
			
		||||
    url: `admin/monitor/monitor/info/${id}/`,
 | 
			
		||||
    method: 'get'
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1619535267426" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="541" width="32" height="32" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style type="text/css"></style></defs><path d="M512 1024C230.4 1024 0 793.6 0 512S230.4 0 512 0s512 230.4 512 512-230.4 512-512 512z m259.2-569.6H480c-12.8 0-25.6 12.8-25.6 25.6v64c0 12.8 12.8 25.6 25.6 25.6h176c12.8 0 25.6 12.8 25.6 25.6v12.8c0 41.6-35.2 76.8-76.8 76.8h-240c-12.8 0-25.6-12.8-25.6-25.6V416c0-41.6 35.2-76.8 76.8-76.8h355.2c12.8 0 25.6-12.8 25.6-25.6v-64c0-12.8-12.8-25.6-25.6-25.6H416c-105.6 0-188.8 86.4-188.8 188.8V768c0 12.8 12.8 25.6 25.6 25.6h374.4c92.8 0 169.6-76.8 169.6-169.6v-144c0-12.8-12.8-25.6-25.6-25.6z" fill="#888888" p-id="542"></path></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 902 B  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 194 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 509 KiB After Width: | Height: | Size: 154 KiB  | 
| 
						 | 
				
			
			@ -35,7 +35,7 @@ export default {
 | 
			
		|||
      const first = matched[0]
 | 
			
		||||
 | 
			
		||||
      if (!this.isDashboard(first)) {
 | 
			
		||||
        matched = [{ path: '/index', meta: { title: '首页' }}].concat(matched)
 | 
			
		||||
        // matched = [{ path: '/index', meta: { title: '首页' }}].concat(matched)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ export default {
 | 
			
		|||
  name: 'RuoYiDoc',
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      url: 'http://doc.ruoyi.vip/ruoyi-vue'
 | 
			
		||||
      url: 'http://django-vue-admin.com'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
| 
						 | 
				
			
			@ -18,4 +18,4 @@ export default {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <svg-icon icon-class="github" @click="goto"/>
 | 
			
		||||
    <svg-icon icon-class="gitee" @click="goto"/>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@ export default {
 | 
			
		|||
  name: 'RuoYiGit',
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      url: 'https://gitee.com/y_project/RuoYi-Vue'
 | 
			
		||||
      url: 'https://gitee.com/liqianglog/django-vue-admin'
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
| 
						 | 
				
			
			@ -18,4 +18,4 @@ export default {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,14 +56,14 @@ export const constantRoutes = [
 | 
			
		|||
  {
 | 
			
		||||
    path: '',
 | 
			
		||||
    component: Layout,
 | 
			
		||||
    redirect: 'index',
 | 
			
		||||
    redirect: '/index',
 | 
			
		||||
    children: [
 | 
			
		||||
      {
 | 
			
		||||
        path: 'index',
 | 
			
		||||
        component: (resolve) => require(['@/views/index'], resolve),
 | 
			
		||||
        name: '首页',
 | 
			
		||||
        meta: { title: '首页', icon: 'dashboard', noCache: true, affix: true }
 | 
			
		||||
      }
 | 
			
		||||
      // {
 | 
			
		||||
      //   path: 'index',
 | 
			
		||||
      //   component: (resolve) => require(['@/views/index'], resolve),
 | 
			
		||||
      //   name: '首页',
 | 
			
		||||
      //   meta: { title: '首页', icon: 'dashboard', noCache: true, affix: true }
 | 
			
		||||
      // }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@ import { getRouters } from '@/api/vadmin/menu'
 | 
			
		|||
import Layout from '@/layout/index'
 | 
			
		||||
import ParentView from '@/components/ParentView';
 | 
			
		||||
import { handleTree } from "@/utils/ruoyi";
 | 
			
		||||
import de from 'element-ui/src/locale/lang/de'
 | 
			
		||||
const permission = {
 | 
			
		||||
  state: {
 | 
			
		||||
    routes: [],
 | 
			
		||||
| 
						 | 
				
			
			@ -51,13 +52,32 @@ function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
 | 
			
		|||
        route.component = Layout
 | 
			
		||||
      } else if (route.component === 'ParentView') {
 | 
			
		||||
        route.component = ParentView
 | 
			
		||||
      } else if (typeof route.component === "string" && route.component === 'Layout/index' && !route.children) { // 首页定制
 | 
			
		||||
        route.meta.affix = true
 | 
			
		||||
        route.children = [
 | 
			
		||||
          {
 | 
			
		||||
            path: 'index',
 | 
			
		||||
            component: 'index',
 | 
			
		||||
            name: route.name,
 | 
			
		||||
            meta: route.meta
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
        route.path = ''
 | 
			
		||||
        route.name = ''
 | 
			
		||||
        route.redirect = '/index'
 | 
			
		||||
        route.alwaysShow = false
 | 
			
		||||
        route.component = Layout
 | 
			
		||||
      } else {
 | 
			
		||||
        route.component = loadView(route.component)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (route.children != null && route.children && route.children.length) {
 | 
			
		||||
      route.children = filterAsyncRouter(route.children, route, type)
 | 
			
		||||
      route.alwaysShow = true
 | 
			
		||||
      if (route.children.length === 1 && route.children[0].path === 'index') {
 | 
			
		||||
        route.alwaysShow = false
 | 
			
		||||
      } else {
 | 
			
		||||
        route.alwaysShow = true
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      delete route['children']
 | 
			
		||||
      delete route['redirect']
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="dashboard-editor-container">
 | 
			
		||||
    <panel-group @handleSetLineChartData="handleSetLineChartData"/>
 | 
			
		||||
 | 
			
		||||
    <el-row style="background:#fff;padding:16px 16px 0;margin-bottom:32px;">
 | 
			
		||||
      <line-chart :chart-data="lineChartData"/>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
    <el-row :gutter="32">
 | 
			
		||||
      <el-col :xs="24" :sm="24" :lg="8">
 | 
			
		||||
        <div class="chart-wrapper">
 | 
			
		||||
          <raddar-chart/>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :xs="24" :sm="24" :lg="8">
 | 
			
		||||
        <div class="chart-wrapper">
 | 
			
		||||
          <pie-chart/>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      <el-col :xs="24" :sm="24" :lg="8">
 | 
			
		||||
        <div class="chart-wrapper">
 | 
			
		||||
          <bar-chart/>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
  import PanelGroup from './dashboard/PanelGroup'
 | 
			
		||||
  import LineChart from './dashboard/LineChart'
 | 
			
		||||
  import RaddarChart from './dashboard/RaddarChart'
 | 
			
		||||
  import PieChart from './dashboard/PieChart'
 | 
			
		||||
  import BarChart from './dashboard/BarChart'
 | 
			
		||||
 | 
			
		||||
  const lineChartData = {
 | 
			
		||||
    newVisitis: {
 | 
			
		||||
      expectedData: [100, 120, 161, 134, 105, 160, 165],
 | 
			
		||||
      actualData: [120, 82, 91, 154, 162, 140, 145]
 | 
			
		||||
    },
 | 
			
		||||
    messages: {
 | 
			
		||||
      expectedData: [200, 192, 120, 144, 160, 130, 140],
 | 
			
		||||
      actualData: [180, 160, 151, 106, 145, 150, 130]
 | 
			
		||||
    },
 | 
			
		||||
    purchases: {
 | 
			
		||||
      expectedData: [80, 100, 121, 104, 105, 90, 100],
 | 
			
		||||
      actualData: [120, 90, 100, 138, 142, 130, 130]
 | 
			
		||||
    },
 | 
			
		||||
    shoppings: {
 | 
			
		||||
      expectedData: [130, 140, 141, 142, 145, 150, 160],
 | 
			
		||||
      actualData: [120, 82, 91, 154, 162, 140, 130]
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  export default {
 | 
			
		||||
    name: 'DashboardAdmin',
 | 
			
		||||
    components: {
 | 
			
		||||
      PanelGroup,
 | 
			
		||||
      LineChart,
 | 
			
		||||
      RaddarChart,
 | 
			
		||||
      PieChart,
 | 
			
		||||
      BarChart,
 | 
			
		||||
    },
 | 
			
		||||
    data() {
 | 
			
		||||
      return {
 | 
			
		||||
        lineChartData: lineChartData.newVisitis
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    methods: {
 | 
			
		||||
      handleSetLineChartData(type) {
 | 
			
		||||
        this.lineChartData = lineChartData[type]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
  .dashboard-editor-container {
 | 
			
		||||
    padding: 32px;
 | 
			
		||||
    background-color: rgb(240, 242, 245);
 | 
			
		||||
    position: relative;
 | 
			
		||||
 | 
			
		||||
    .github-corner {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 0px;
 | 
			
		||||
      border: 0;
 | 
			
		||||
      right: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .chart-wrapper {
 | 
			
		||||
      background: #fff;
 | 
			
		||||
      padding: 16px 16px 0;
 | 
			
		||||
      margin-bottom: 32px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @media (max-width: 1024px) {
 | 
			
		||||
    .chart-wrapper {
 | 
			
		||||
      padding: 8px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,41 +1,51 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="app-container home">
 | 
			
		||||
    <el-row :gutter="20">
 | 
			
		||||
<!--      <el-col :sm="24" :lg="24">-->
 | 
			
		||||
<!--        <blockquote class="text-warning" style="font-size: 14px">-->
 | 
			
		||||
<!--          领取阿里云通用云产品1888优惠券-->
 | 
			
		||||
<!--          <br />-->
 | 
			
		||||
<!--          <el-link-->
 | 
			
		||||
<!--            href="https://www.aliyun.com/minisite/goods?source=5176.11533457&userCode=jpef8a71"-->
 | 
			
		||||
<!--            type="primary"-->
 | 
			
		||||
<!--            target="_blank"-->
 | 
			
		||||
<!--            >https://www.aliyun.com/minisite/goods?source=5176.11533457&userCode=jpef8a71</el-link-->
 | 
			
		||||
<!--          >-->
 | 
			
		||||
<!--          <br />-->
 | 
			
		||||
<!--          领取腾讯云通用云产品2860优惠券-->
 | 
			
		||||
<!--          <br />-->
 | 
			
		||||
<!--          <el-link-->
 | 
			
		||||
<!--            href="https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console"-->
 | 
			
		||||
<!--            type="primary"-->
 | 
			
		||||
<!--            target="_blank"-->
 | 
			
		||||
<!--            >https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console</el-link-->
 | 
			
		||||
<!--          >-->
 | 
			
		||||
<!--          <br />-->
 | 
			
		||||
<!--          阿里云服务器折扣区-->
 | 
			
		||||
<!--          <el-link href="https://www.aliyun.com/minisite/goods?source=5176.11533457&userCode=jpef8a71" type="primary" target="_blank"-->
 | 
			
		||||
<!--            >>☛☛点我进入☚☚</el-link-->
 | 
			
		||||
<!--          >-->
 | 
			
		||||
<!--              腾讯云服务器秒杀区-->
 | 
			
		||||
<!--          <el-link href="http://txy.ruoyi.vip" type="primary" target="_blank"-->
 | 
			
		||||
<!--            >>☛☛点我进入☚☚</el-link-->
 | 
			
		||||
<!--          ><br />-->
 | 
			
		||||
<!--          <h4 class="text-danger">-->
 | 
			
		||||
<!--            云产品通用红包,可叠加官网常规优惠使用。(仅限新用户)-->
 | 
			
		||||
<!--          </h4>-->
 | 
			
		||||
<!--        </blockquote>-->
 | 
			
		||||
      <el-col :sm="24" :lg="24">
 | 
			
		||||
        <blockquote class="text-warning" style="font-size: 14px">
 | 
			
		||||
          领取阿里云通用云产品1888优惠券,精选云服务器 ECS 1核2G 87.12元/年
 | 
			
		||||
          <br/>
 | 
			
		||||
          <el-link
 | 
			
		||||
            href="https://www.aliyun.com/activity/new?source=5176.11533457&userCode=jpef8a71"
 | 
			
		||||
            type="primary"
 | 
			
		||||
            target="_blank"
 | 
			
		||||
          >https://www.aliyun.com/activity/new?source=5176.11533457&userCode=jpef8a71
 | 
			
		||||
          </el-link
 | 
			
		||||
          >
 | 
			
		||||
          <br/>
 | 
			
		||||
          领取腾讯云通用云产品2860优惠券,半价购买,满200减100,满500减250
 | 
			
		||||
          <br/>
 | 
			
		||||
          <el-link
 | 
			
		||||
            href="https://curl.qcloud.com/yWalJsQY"
 | 
			
		||||
            type="primary"
 | 
			
		||||
            target="_blank"
 | 
			
		||||
          >https://curl.qcloud.com/yWalJsQY
 | 
			
		||||
          </el-link
 | 
			
		||||
          >
 | 
			
		||||
          <br/>
 | 
			
		||||
          阿里云服务器折扣区
 | 
			
		||||
          <el-link href="https://www.aliyun.com/minisite/goods?source=5176.11533457&userCode=jpef8a71" type="primary"
 | 
			
		||||
                   target="_blank"
 | 
			
		||||
          >>☛☛点我进入☚☚
 | 
			
		||||
          </el-link
 | 
			
		||||
          >
 | 
			
		||||
              腾讯云服务器秒杀区
 | 
			
		||||
          <el-link href="https://curl.qcloud.com/cRu8Ljf8" type="primary" target="_blank"
 | 
			
		||||
          >>☛☛点我进入☚☚
 | 
			
		||||
          </el-link
 | 
			
		||||
          >
 | 
			
		||||
          <br/>
 | 
			
		||||
          <h4 class="text-danger">
 | 
			
		||||
            云产品通用红包,可叠加官网常规优惠使用。(仅限新用户)
 | 
			
		||||
            <br>
 | 
			
		||||
            通过推广链接购买服务器者,可免费提供搭建环境服务一次。
 | 
			
		||||
          </h4>
 | 
			
		||||
          <h4 class="text-danger">
 | 
			
		||||
 | 
			
		||||
<!--        <hr />-->
 | 
			
		||||
<!--      </el-col>-->
 | 
			
		||||
          </h4>
 | 
			
		||||
        </blockquote>
 | 
			
		||||
        <hr/>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
    <el-row :gutter="20">
 | 
			
		||||
      <el-col :sm="24" :lg="12" style="padding-left: 20px">
 | 
			
		||||
| 
						 | 
				
			
			@ -58,14 +68,16 @@
 | 
			
		|||
            icon="el-icon-cloudy"
 | 
			
		||||
            plain
 | 
			
		||||
            @click="goTarget('https://gitee.com/liqianglog/django-vue-admin')"
 | 
			
		||||
            >访问码云</el-button
 | 
			
		||||
          >访问码云
 | 
			
		||||
          </el-button
 | 
			
		||||
          >
 | 
			
		||||
          <el-button
 | 
			
		||||
            size="mini"
 | 
			
		||||
            icon="el-icon-s-home"
 | 
			
		||||
            plain
 | 
			
		||||
            @click="goTarget('https://django-vue-admin.com')"
 | 
			
		||||
            >访问主页</el-button
 | 
			
		||||
            @click="goTarget('http://django-vue-admin.com')"
 | 
			
		||||
          >访问主页
 | 
			
		||||
          </el-button
 | 
			
		||||
          >
 | 
			
		||||
        </p>
 | 
			
		||||
      </el-col>
 | 
			
		||||
| 
						 | 
				
			
			@ -83,6 +95,7 @@
 | 
			
		|||
              <li>Python</li>
 | 
			
		||||
              <li>Django</li>
 | 
			
		||||
              <li>django-redis</li>
 | 
			
		||||
              <li>django-celery-beat</li>
 | 
			
		||||
              <li>django-rest-framework</li>
 | 
			
		||||
              <li>django-rest-framework-jwt</li>
 | 
			
		||||
              <li>...</li>
 | 
			
		||||
| 
						 | 
				
			
			@ -103,7 +116,7 @@
 | 
			
		|||
        </el-row>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
    <el-divider />
 | 
			
		||||
    <el-divider/>
 | 
			
		||||
    <el-row :gutter="20">
 | 
			
		||||
      <el-col :xs="24" :sm="24" :md="12" :lg="8">
 | 
			
		||||
        <el-card class="update-log">
 | 
			
		||||
| 
						 | 
				
			
			@ -112,18 +125,28 @@
 | 
			
		|||
          </div>
 | 
			
		||||
          <div class="body">
 | 
			
		||||
            <p>
 | 
			
		||||
              <i class="el-icon-s-promotion"></i> 官网:<el-link
 | 
			
		||||
                href="https://django-vue-admin.com"
 | 
			
		||||
              <i class="el-icon-s-promotion"></i> 官网:
 | 
			
		||||
              <el-link
 | 
			
		||||
                href="http://django-vue-admin.com"
 | 
			
		||||
                target="_blank"
 | 
			
		||||
                >https://django-vue-admin.com</el-link
 | 
			
		||||
              >http://django-vue-admin.com
 | 
			
		||||
              </el-link
 | 
			
		||||
              >
 | 
			
		||||
            </p>
 | 
			
		||||
            <p>
 | 
			
		||||
              <i class="el-icon-user-solid"></i> QQ群
 | 
			
		||||
              <a href="https://qm.qq.com/cgi-bin/qm/qr?k=E2fte0FJlSr56-thAmabGcV3Lv6vLsp9&jump_from=webapi" target="_blank"
 | 
			
		||||
                > 812482043</a
 | 
			
		||||
              <i class="el-icon-user-solid"></i> QQ群:
 | 
			
		||||
              <a href="https://qm.qq.com/cgi-bin/qm/qr?k=E2fte0FJlSr56-thAmabGcV3Lv6vLsp9&jump_from=webapi"
 | 
			
		||||
                 target="_blank"
 | 
			
		||||
              > 812482043</a
 | 
			
		||||
              >
 | 
			
		||||
            </p>
 | 
			
		||||
            <p>
 | 
			
		||||
              <img
 | 
			
		||||
                src="@/assets/images/qq.jpg"
 | 
			
		||||
                alt="donate"
 | 
			
		||||
                width="25%"
 | 
			
		||||
              />
 | 
			
		||||
            </p>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-col>
 | 
			
		||||
| 
						 | 
				
			
			@ -133,6 +156,28 @@
 | 
			
		|||
            <span>更新日志</span>
 | 
			
		||||
          </div>
 | 
			
		||||
          <el-collapse accordion>
 | 
			
		||||
            <el-collapse-item title="">
 | 
			
		||||
              <template slot="title">
 | 
			
		||||
                v1.1.0 - 2021-05-05   
 | 
			
		||||
                <el-badge value="new" class="item" style="padding-top: 10px;padding-left: 10px;"/>
 | 
			
		||||
              </template>
 | 
			
		||||
              <ol>
 | 
			
		||||
                <li>新增服务监控功能</li>
 | 
			
		||||
                <li>新增操作日志功能</li>
 | 
			
		||||
                <li>新增导入功能</li>
 | 
			
		||||
                <li>新增celery定时任务</li>
 | 
			
		||||
                <li>新增消息通知功能</li>
 | 
			
		||||
                <li>新增后端接口文档</li>
 | 
			
		||||
                <li>新增docker-compose部署</li>
 | 
			
		||||
                <li>新增支持重写用户模型</li>
 | 
			
		||||
                <li>数据权限完善</li>
 | 
			
		||||
                <li>登录日志优化</li>
 | 
			
		||||
                <li>后端代码架构优化</li>
 | 
			
		||||
                <li>文件管理功能完善</li>
 | 
			
		||||
                <li>修复创建用户密码问题</li>
 | 
			
		||||
                <li>其他细节优化</li>
 | 
			
		||||
              </ol>
 | 
			
		||||
            </el-collapse-item>
 | 
			
		||||
            <el-collapse-item title="v1.0.0 - 2021-03-01">
 | 
			
		||||
              <ol>
 | 
			
		||||
                <li>dvAdmin前后端分离系统正式发布</li>
 | 
			
		||||
| 
						 | 
				
			
			@ -147,14 +192,14 @@
 | 
			
		|||
            <span>捐赠支持</span>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="body">
 | 
			
		||||
<!--            <img-->
 | 
			
		||||
<!--              src="https://oscimg.oschina.net/oscnet/up-d6695f82666e5018f715c41cb7ee60d3b73.png"-->
 | 
			
		||||
<!--              alt="donate"-->
 | 
			
		||||
<!--              width="100%"-->
 | 
			
		||||
<!--            />-->
 | 
			
		||||
            <span style="display: inline-block; height: 30px; line-height: 30px"
 | 
			
		||||
              >加群交流就是最好的支持~</span
 | 
			
		||||
            >
 | 
			
		||||
            <img
 | 
			
		||||
              src="@/assets/images/collection_code.jpg"
 | 
			
		||||
              alt="donate"
 | 
			
		||||
              width="100%"
 | 
			
		||||
            />
 | 
			
		||||
            <!--            <span style="display: inline-block; height: 30px; line-height: 30px"-->
 | 
			
		||||
            <!--              >加群交流就是最好的支持~</span-->
 | 
			
		||||
            <!--            >-->
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-col>
 | 
			
		||||
| 
						 | 
				
			
			@ -163,83 +208,85 @@
 | 
			
		|||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
export default {
 | 
			
		||||
  name: "index",
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      // 版本号
 | 
			
		||||
      version: "1.0.0",
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    goTarget(href) {
 | 
			
		||||
      window.open(href, "_blank");
 | 
			
		||||
  export default {
 | 
			
		||||
    name: "index",
 | 
			
		||||
    data() {
 | 
			
		||||
      return {
 | 
			
		||||
        // 版本号
 | 
			
		||||
        version: "1.1.0",
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
    methods: {
 | 
			
		||||
      goTarget(href) {
 | 
			
		||||
        window.open(href, "_blank");
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="scss">
 | 
			
		||||
.home {
 | 
			
		||||
  blockquote {
 | 
			
		||||
    padding: 10px 20px;
 | 
			
		||||
    margin: 0 0 20px;
 | 
			
		||||
    font-size: 17.5px;
 | 
			
		||||
    border-left: 5px solid #eee;
 | 
			
		||||
  }
 | 
			
		||||
  hr {
 | 
			
		||||
    margin-top: 20px;
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
    border: 0;
 | 
			
		||||
    border-top: 1px solid #eee;
 | 
			
		||||
  }
 | 
			
		||||
  .col-item {
 | 
			
		||||
    margin-bottom: 20px;
 | 
			
		||||
  }
 | 
			
		||||
  .home {
 | 
			
		||||
    blockquote {
 | 
			
		||||
      padding: 10px 20px;
 | 
			
		||||
      margin: 0 0 20px;
 | 
			
		||||
      font-size: 17.5px;
 | 
			
		||||
      border-left: 5px solid #eee;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  ul {
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
  }
 | 
			
		||||
    hr {
 | 
			
		||||
      margin-top: 20px;
 | 
			
		||||
      margin-bottom: 20px;
 | 
			
		||||
      border: 0;
 | 
			
		||||
      border-top: 1px solid #eee;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  color: #676a6c;
 | 
			
		||||
  overflow-x: hidden;
 | 
			
		||||
    .col-item {
 | 
			
		||||
      margin-bottom: 20px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  ul {
 | 
			
		||||
    list-style-type: none;
 | 
			
		||||
  }
 | 
			
		||||
    ul {
 | 
			
		||||
      padding: 0;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  h4 {
 | 
			
		||||
    margin-top: 0px;
 | 
			
		||||
  }
 | 
			
		||||
    font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
    color: #676a6c;
 | 
			
		||||
    overflow-x: hidden;
 | 
			
		||||
 | 
			
		||||
  h2 {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
    font-size: 26px;
 | 
			
		||||
    font-weight: 100;
 | 
			
		||||
  }
 | 
			
		||||
    ul {
 | 
			
		||||
      list-style-type: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  p {
 | 
			
		||||
    margin-top: 10px;
 | 
			
		||||
    h4 {
 | 
			
		||||
      margin-top: 0px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    b {
 | 
			
		||||
      font-weight: 700;
 | 
			
		||||
    h2 {
 | 
			
		||||
      margin-top: 10px;
 | 
			
		||||
      font-size: 26px;
 | 
			
		||||
      font-weight: 100;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    p {
 | 
			
		||||
      margin-top: 10px;
 | 
			
		||||
 | 
			
		||||
      b {
 | 
			
		||||
        font-weight: 700;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .update-log {
 | 
			
		||||
      ol {
 | 
			
		||||
        display: block;
 | 
			
		||||
        list-style-type: decimal;
 | 
			
		||||
        margin-block-start: 1em;
 | 
			
		||||
        margin-block-end: 1em;
 | 
			
		||||
        margin-inline-start: 0;
 | 
			
		||||
        margin-inline-end: 0;
 | 
			
		||||
        padding-inline-start: 40px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .update-log {
 | 
			
		||||
    ol {
 | 
			
		||||
      display: block;
 | 
			
		||||
      list-style-type: decimal;
 | 
			
		||||
      margin-block-start: 1em;
 | 
			
		||||
      margin-block-end: 1em;
 | 
			
		||||
      margin-inline-start: 0;
 | 
			
		||||
      margin-inline-end: 0;
 | 
			
		||||
      padding-inline-start: 40px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,7 +48,10 @@
 | 
			
		|||
    </el-form>
 | 
			
		||||
    <!--  底部  -->
 | 
			
		||||
    <div class="el-login-footer">
 | 
			
		||||
      <span>Copyright © 2018-2021 ruoyi.vip All Rights Reserved.</span>
 | 
			
		||||
      <span>Copyright © 2018-2021 django-vue-admin.com All Rights Reserved.</span> |
 | 
			
		||||
      <a href="https://beian.miit.gov.cn/#/Integrated/index"
 | 
			
		||||
               target="_blank"
 | 
			
		||||
      > 晋ICP备18005113号-3</a>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,216 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="instrument-board">
 | 
			
		||||
    <div v-if="showTopTitle && haveMultipleData" class="instrument-board-title">
 | 
			
		||||
      <el-select :value="topTitle"
 | 
			
		||||
                 @change="chooseDisplayInstrumentBoardData"
 | 
			
		||||
      >
 | 
			
		||||
        <el-option
 | 
			
		||||
          v-for="(item,index) in instrumentBoardData"
 | 
			
		||||
          :key="index"
 | 
			
		||||
          :label="item.name || item['dir_name']"
 | 
			
		||||
          :value="index"
 | 
			
		||||
        >
 | 
			
		||||
        </el-option>
 | 
			
		||||
      </el-select>
 | 
			
		||||
 | 
			
		||||
    </div>
 | 
			
		||||
    <div v-else-if="showTopTitle" class="instrument-board-title">
 | 
			
		||||
      {{ topTitle }}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div :id="ringGraphId" class="instrument-board-body"></div>
 | 
			
		||||
    <div v-if="showSubTitle"
 | 
			
		||||
         class="instrument-board-subtitle"
 | 
			
		||||
         :title="subTitle.title"
 | 
			
		||||
    >{{ subTitle.content }}
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import VueTypes from 'vue-types'
 | 
			
		||||
// 引入基本模板,按需加载
 | 
			
		||||
const echarts = require('echarts/lib/echarts')
 | 
			
		||||
require('echarts/lib/chart/gauge')
 | 
			
		||||
 | 
			
		||||
// 仪表盘颜色范围
 | 
			
		||||
const NORMAL_COLOR = {
 | 
			
		||||
  color: '#28BCFE',
 | 
			
		||||
  itemColor: ['#25bfff', '#5284de', '#2a95f9']
 | 
			
		||||
}
 | 
			
		||||
const WARNING_COLOR = {
 | 
			
		||||
  color: '#e6a23c',
 | 
			
		||||
  itemColor: ['#e6a23c', '#cc8b1d', '#ffaf18']
 | 
			
		||||
}
 | 
			
		||||
const DANGER_COLOR = {
 | 
			
		||||
  color: '#F56C6C',
 | 
			
		||||
  itemColor: ['#fd666d', '#cf1717', '#b31212']
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'InstrumentBoard',
 | 
			
		||||
  props: {
 | 
			
		||||
    // 组件key
 | 
			
		||||
    ringGraphKey: VueTypes.string.isRequired,
 | 
			
		||||
    // 上标题
 | 
			
		||||
    showTopTitle: VueTypes.bool.def(false),
 | 
			
		||||
    // 下标题
 | 
			
		||||
    showSubTitle: VueTypes.bool.def(false),
 | 
			
		||||
    // top title 配置映射
 | 
			
		||||
    topTitleKeyToNameMapping: VueTypes.object.def({
 | 
			
		||||
      cpu: 'CPU使用率',
 | 
			
		||||
      memory: '内存使用率'
 | 
			
		||||
    }),
 | 
			
		||||
    instrumentBoardData: VueTypes.any.isRequired
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      // 当前显示的数据
 | 
			
		||||
      currentInstrumentBoardData: {}
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    // 仪表盘是否存在多个数据
 | 
			
		||||
    haveMultipleData() {
 | 
			
		||||
      return this.instrumentBoardData instanceof Array && this.instrumentBoardData.length > 0
 | 
			
		||||
    },
 | 
			
		||||
    // 使用率
 | 
			
		||||
    ringRate() {
 | 
			
		||||
      let ringRate = this.currentInstrumentBoardData.rate
 | 
			
		||||
      ringRate = ringRate < 1 ? ringRate * 100 : ringRate
 | 
			
		||||
      return parseFloat(ringRate.toFixed(4))
 | 
			
		||||
    },
 | 
			
		||||
    // 仪表盘id
 | 
			
		||||
    ringGraphId() {
 | 
			
		||||
      return `${this.ringGraphKey}UsingRate`
 | 
			
		||||
    },
 | 
			
		||||
    // 上方标题
 | 
			
		||||
    topTitle() {
 | 
			
		||||
      return this.currentInstrumentBoardData['dir_name'] || this.topTitleKeyToNameMapping[this.ringGraphKey] || this.ringGraphKey
 | 
			
		||||
    },
 | 
			
		||||
    // 下方标题
 | 
			
		||||
    subTitle() {
 | 
			
		||||
      let used = this.currentInstrumentBoardData['used'] ? this.currentInstrumentBoardData['used'] + '/' : ''
 | 
			
		||||
      let total = this.currentInstrumentBoardData['total'] ? this.currentInstrumentBoardData['total'] : ''
 | 
			
		||||
      let unit = this.currentInstrumentBoardData['unit'] ? ` (${this.currentInstrumentBoardData['unit']})` : ''
 | 
			
		||||
      let content = `${used}${total}${unit} `
 | 
			
		||||
      let title = (this.currentInstrumentBoardData['used'] ? '已用/' : '') + '总量(单位)'
 | 
			
		||||
      return { content, title }
 | 
			
		||||
    },
 | 
			
		||||
    // 使用率样式配置
 | 
			
		||||
    usingRateStyle() {
 | 
			
		||||
      return {
 | 
			
		||||
        fontSize: 18,
 | 
			
		||||
        ...this.getCircleColor(this.ringRate)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  mounted() {
 | 
			
		||||
    if (this.haveMultipleData) {
 | 
			
		||||
      this.currentInstrumentBoardData = this.instrumentBoardData[0]
 | 
			
		||||
    } else {
 | 
			
		||||
      this.currentInstrumentBoardData = this.instrumentBoardData
 | 
			
		||||
    }
 | 
			
		||||
    this.drawBar()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    drawBar() {
 | 
			
		||||
      let currentRate = [this.ringRate]
 | 
			
		||||
      // 基于dom,初始化echarts实例
 | 
			
		||||
      let RingGraph = echarts.init(document.getElementById(this.ringGraphId))
 | 
			
		||||
 | 
			
		||||
      let option = {
 | 
			
		||||
        title: {
 | 
			
		||||
          text: currentRate + '%',
 | 
			
		||||
          textStyle: this.usingRateStyle,
 | 
			
		||||
          itemGap: 10,
 | 
			
		||||
          left: 'center',
 | 
			
		||||
          top: '45%'
 | 
			
		||||
        },
 | 
			
		||||
        angleAxis: {
 | 
			
		||||
          max: 100,
 | 
			
		||||
          clockwise: true, // 逆时针
 | 
			
		||||
          // 隐藏刻度线
 | 
			
		||||
          show: false
 | 
			
		||||
        },
 | 
			
		||||
        radiusAxis: {
 | 
			
		||||
          type: 'category',
 | 
			
		||||
          show: true,
 | 
			
		||||
          axisLabel: {
 | 
			
		||||
            show: false
 | 
			
		||||
          },
 | 
			
		||||
          axisLine: {
 | 
			
		||||
            show: false
 | 
			
		||||
          },
 | 
			
		||||
          axisTick: {
 | 
			
		||||
            show: false
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        polar: {
 | 
			
		||||
          center: ['50%', '50%'], // 坐标中心位置
 | 
			
		||||
          radius: '100%' //图形大小
 | 
			
		||||
        },
 | 
			
		||||
        series: [{
 | 
			
		||||
          type: 'bar',
 | 
			
		||||
          data: currentRate,
 | 
			
		||||
          showBackground: true,
 | 
			
		||||
          backgroundStyle: {
 | 
			
		||||
            color: '#BDEBFF' // 底圈颜色
 | 
			
		||||
          },
 | 
			
		||||
          coordinateSystem: 'polar',
 | 
			
		||||
          roundCap: true,
 | 
			
		||||
          barWidth: 15,
 | 
			
		||||
          itemStyle: {
 | 
			
		||||
            normal: {
 | 
			
		||||
              opacity: 1,
 | 
			
		||||
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
 | 
			
		||||
                offset: 0,
 | 
			
		||||
                color: this.usingRateStyle.itemColor[0] || '#25BFFF'
 | 
			
		||||
              }, {
 | 
			
		||||
                offset: 1,
 | 
			
		||||
                color: this.usingRateStyle.itemColor[1] || '#5284DE'
 | 
			
		||||
              }]),
 | 
			
		||||
              shadowBlur: 1,
 | 
			
		||||
              shadowColor: this.usingRateStyle.itemColor[2] || '#2A95F9'
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }]
 | 
			
		||||
      }
 | 
			
		||||
      // 绘制图表
 | 
			
		||||
      RingGraph.setOption(option)
 | 
			
		||||
    },
 | 
			
		||||
    // 仪表盘样式-颜色
 | 
			
		||||
    getCircleColor(usingRate) {
 | 
			
		||||
      if (usingRate < 60) {
 | 
			
		||||
        return NORMAL_COLOR
 | 
			
		||||
      } else if (usingRate > 60 && usingRate < 80) {
 | 
			
		||||
        return WARNING_COLOR
 | 
			
		||||
      } else if (usingRate > 80) {
 | 
			
		||||
        return DANGER_COLOR
 | 
			
		||||
      }
 | 
			
		||||
      return NORMAL_COLOR
 | 
			
		||||
    },
 | 
			
		||||
    chooseDisplayInstrumentBoardData(index) {
 | 
			
		||||
      this.currentInstrumentBoardData = this.instrumentBoardData[index]
 | 
			
		||||
      this.drawBar()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.instrument-board-title {
 | 
			
		||||
  font-weight: bolder;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.instrument-board-body {
 | 
			
		||||
  min-height: 200px;
 | 
			
		||||
  min-width: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.instrument-board-subtitle {
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,195 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div>
 | 
			
		||||
    <div class="line-chart-title">
 | 
			
		||||
      <div class="line-chart-name">{{ chartTitle }}</div>
 | 
			
		||||
      <div class="line-chart-time-radio">
 | 
			
		||||
        <el-radio-group v-model="timeLimit" @change="changeTimeLimit">
 | 
			
		||||
          <el-radio-button
 | 
			
		||||
            v-for="item in Object.keys(TIME_LIMIT_SETTING)"
 | 
			
		||||
            :label="TIME_LIMIT_SETTING[item].name"
 | 
			
		||||
            :key="TIME_LIMIT_SETTING[item].key"
 | 
			
		||||
          ></el-radio-button>
 | 
			
		||||
        </el-radio-group>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div :id="lineChartId" class="line-chart-body"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import VueTypes from 'vue-types'
 | 
			
		||||
// 引入基本模板,按需加载
 | 
			
		||||
import echarts from 'echarts'
 | 
			
		||||
import moment from 'moment'
 | 
			
		||||
import { getMonitorLogs } from '@/api/vadmin/monitor/server'
 | 
			
		||||
 | 
			
		||||
const MONTH = moment().month()
 | 
			
		||||
const YEAR = moment().year()
 | 
			
		||||
const TODAY = moment().format('YYYY-MM-DD')
 | 
			
		||||
const YESTERDAY = moment().subtract(1, 'days').format('YYYY-MM-DD')
 | 
			
		||||
const LAST_SEVEN_DAYS = moment().subtract(7, 'days').format('YYYY-MM-DD')
 | 
			
		||||
const LAST_THIRTY_DAYS = moment().subtract(30, 'days').format('YYYY-MM-DD')
 | 
			
		||||
 | 
			
		||||
// 时间段筛选配置
 | 
			
		||||
const TIME_LIMIT_SETTING = {
 | 
			
		||||
  'yesterday': {
 | 
			
		||||
    key: 'yesterday',
 | 
			
		||||
    name: '昨天',
 | 
			
		||||
    timeRange: [
 | 
			
		||||
      `${YESTERDAY} 00:00:00`,
 | 
			
		||||
      `${YESTERDAY} 23:59:59`
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  'today': {
 | 
			
		||||
    key: 'today',
 | 
			
		||||
    name: '今天',
 | 
			
		||||
    timeRange: [
 | 
			
		||||
      `${TODAY} 00:00:00`,
 | 
			
		||||
      `${TODAY} 23:59:59`
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  'latestWeek': {
 | 
			
		||||
    key: 'latestWeek',
 | 
			
		||||
    name: '最近7天',
 | 
			
		||||
    timeRange: [
 | 
			
		||||
      `${LAST_SEVEN_DAYS} 00:00:00`,
 | 
			
		||||
      `${TODAY} 23:59:59`
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  'latestMonth': {
 | 
			
		||||
    key: 'latestMonth',
 | 
			
		||||
    name: '最近30天',
 | 
			
		||||
    timeRange: [
 | 
			
		||||
      `${LAST_THIRTY_DAYS} 00:00:00`,
 | 
			
		||||
      `${TODAY} 23:59:59`
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// 默认显示时间
 | 
			
		||||
const DEFAULT_TIME = '今天'
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: 'LineChart',
 | 
			
		||||
  props: {
 | 
			
		||||
    serverInfo: VueTypes.object.isRequired,
 | 
			
		||||
    lineChartKey: VueTypes.string.isRequired,
 | 
			
		||||
    chartTitle: VueTypes.string.isRequired,
 | 
			
		||||
    chartData: VueTypes.array.isRequired,
 | 
			
		||||
    chartTime: VueTypes.array.isRequired,
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      TIME_LIMIT_SETTING,
 | 
			
		||||
      timeLimit: DEFAULT_TIME,
 | 
			
		||||
      lineChartId: this.lineChartKey + 'Chart',
 | 
			
		||||
      lineChartData: this.chartData,
 | 
			
		||||
      lineChartTime: this.chartTime
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  mounted() {
 | 
			
		||||
    this.drawBar()
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    timeLimitNames() {
 | 
			
		||||
      return Object.keys(TIME_LIMIT_SETTING).map(item => {
 | 
			
		||||
        return TIME_LIMIT_SETTING[item].name
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    currentDateIndex() {
 | 
			
		||||
      return this.timeLimitNames.indexOf(this.timeLimit)
 | 
			
		||||
    },
 | 
			
		||||
    currentTimeLimitSetting() {
 | 
			
		||||
      return TIME_LIMIT_SETTING[Object.keys(TIME_LIMIT_SETTING)[this.currentDateIndex]]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    drawBar() {
 | 
			
		||||
      // 基于dom,初始化echarts实例
 | 
			
		||||
      let barGraph = echarts.init(document.getElementById(this.lineChartId))
 | 
			
		||||
      // 绘制图表
 | 
			
		||||
      barGraph.setOption({
 | 
			
		||||
        tooltip : {
 | 
			
		||||
          trigger: 'axis',
 | 
			
		||||
          axisPointer: {
 | 
			
		||||
            type: 'cross',
 | 
			
		||||
            label: {
 | 
			
		||||
              backgroundColor: '#6a7985',
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        legend: {
 | 
			
		||||
          left: 'center',
 | 
			
		||||
          data: [],
 | 
			
		||||
          bottom: 0
 | 
			
		||||
        },
 | 
			
		||||
        xAxis: {
 | 
			
		||||
          type: 'category',
 | 
			
		||||
          name: 'x',
 | 
			
		||||
          splitLine: { show: false },
 | 
			
		||||
          data:this.lineChartTime,
 | 
			
		||||
          offset:20
 | 
			
		||||
        },
 | 
			
		||||
        grid: {
 | 
			
		||||
          left: '1%',
 | 
			
		||||
          right: '2%',
 | 
			
		||||
          bottom: '8%',
 | 
			
		||||
          containLabel: true
 | 
			
		||||
        },
 | 
			
		||||
        yAxis: {
 | 
			
		||||
          type: 'value',
 | 
			
		||||
          name: '使用率',
 | 
			
		||||
          axisLabel: {
 | 
			
		||||
            show: true,
 | 
			
		||||
            interval: 'auto',
 | 
			
		||||
            formatter: '{value}%'
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        series: [
 | 
			
		||||
          {
 | 
			
		||||
            name: '使用率',
 | 
			
		||||
            type: 'line',
 | 
			
		||||
            data: this.lineChartData,
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    changeTimeLimit(value) {
 | 
			
		||||
      this.timeLimit = value
 | 
			
		||||
      this.getCurrentServerMonitorLogs()
 | 
			
		||||
    },
 | 
			
		||||
    getCurrentServerMonitorLogs() {
 | 
			
		||||
      getMonitorLogs(this.serverInfo.id, {as: JSON.stringify({create_datetime__range: this.currentTimeLimitSetting.timeRange})}).then(results => {
 | 
			
		||||
        this.lineChartData = results.data[this.lineChartKey]
 | 
			
		||||
        this.lineChartTime = results.data["datetime"]
 | 
			
		||||
        this.drawBar()
 | 
			
		||||
      }).catch(error => {
 | 
			
		||||
        this.$message.warning(error.msg || `获取${this.chartTitle}数据失败!`)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.line-chart-name {
 | 
			
		||||
  font-weight: bolder;
 | 
			
		||||
  width: 20%;
 | 
			
		||||
  min-width: 30px;
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.line-chart-time-radio {
 | 
			
		||||
  font-weight: bolder;
 | 
			
		||||
  width: 80%;
 | 
			
		||||
  min-width: 200px;
 | 
			
		||||
  text-align: right;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.line-chart-body {
 | 
			
		||||
  min-height: 300px;
 | 
			
		||||
  min-width: 300px;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,210 +1,500 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div class="app-container">
 | 
			
		||||
    <el-row>
 | 
			
		||||
      <el-col :span="12" class="card-box">
 | 
			
		||||
        <el-card>
 | 
			
		||||
          <div slot="header"><span>CPU</span></div>
 | 
			
		||||
          <div class="el-table el-table--enable-row-hover el-table--medium">
 | 
			
		||||
            <table cellspacing="0" style="width: 100%;">
 | 
			
		||||
              <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">属性</div></th>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">值</div></th>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </thead>
 | 
			
		||||
              <tbody>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">核心数</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.cpuNum }}</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">用户使用率</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.used }}%</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">系统使用率</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.sys }}%</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">当前空闲率</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.cpu">{{ server.cpu.free }}%</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    <!-- 监控控制 -->
 | 
			
		||||
    <div class="server-monitor-control">
 | 
			
		||||
      <!-- 监控启用开关 -->
 | 
			
		||||
      <div class="control-server-monitor same-block">
 | 
			
		||||
        开启监控:
 | 
			
		||||
        <el-switch
 | 
			
		||||
          v-model="isOpeningMonitor"
 | 
			
		||||
          active-color="#13ce66"
 | 
			
		||||
          inactive-color="#ff4949"
 | 
			
		||||
          title="控制所有监控项"
 | 
			
		||||
          @change="changeMonitorStatus"
 | 
			
		||||
        >
 | 
			
		||||
        </el-switch>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- 更新频率设置 -->
 | 
			
		||||
      <div class="monitor-update-interval same-block">
 | 
			
		||||
        监控频率:
 | 
			
		||||
        <el-input-number v-model="monitorUpdateInterval"
 | 
			
		||||
                         label=""
 | 
			
		||||
                         class="monitor-update-interval-blank"
 | 
			
		||||
                         controls-position="right"
 | 
			
		||||
                         :min="minMonitorUpdateInterval"
 | 
			
		||||
                         @input="handleIntervalChange"
 | 
			
		||||
 | 
			
		||||
      <el-col :span="12" class="card-box">
 | 
			
		||||
        <el-card>
 | 
			
		||||
          <div slot="header"><span>内存</span></div>
 | 
			
		||||
          <div class="el-table el-table--enable-row-hover el-table--medium">
 | 
			
		||||
            <table cellspacing="0" style="width: 100%;">
 | 
			
		||||
              <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">属性</div></th>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">内存</div></th>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">JVM</div></th>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </thead>
 | 
			
		||||
              <tbody>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">总内存</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.mem">{{ server.mem.total }}G</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.total }}M</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">已用内存</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.mem">{{ server.mem.used}}G</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.used}}M</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">剩余内存</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.mem">{{ server.mem.free }}G</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.free }}M</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">使用率</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.mem" :class="{'text-danger': server.mem.usage > 80}">{{ server.mem.usage }}%</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.jvm" :class="{'text-danger': server.jvm.usage > 80}">{{ server.jvm.usage }}%</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-col>
 | 
			
		||||
        ></el-input-number>
 | 
			
		||||
        <el-select v-model="intervalType"
 | 
			
		||||
                   class="monitor-update-interval-unit"
 | 
			
		||||
                   @change="selectIntervalType"
 | 
			
		||||
        >
 | 
			
		||||
          <el-option
 | 
			
		||||
            v-for="item in Object.keys(INTERVAL_ID_TO_TYPE_MAPPING)"
 | 
			
		||||
            :key="INTERVAL_ID_TO_TYPE_MAPPING[item].type"
 | 
			
		||||
            :label="INTERVAL_ID_TO_TYPE_MAPPING[item].name"
 | 
			
		||||
            :value="INTERVAL_ID_TO_TYPE_MAPPING[item].name">
 | 
			
		||||
          </el-option>
 | 
			
		||||
        </el-select>
 | 
			
		||||
 | 
			
		||||
      <el-col :span="24" class="card-box">
 | 
			
		||||
        <el-card>
 | 
			
		||||
          <div slot="header">
 | 
			
		||||
            <span>服务器信息</span>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="el-table el-table--enable-row-hover el-table--medium">
 | 
			
		||||
            <table cellspacing="0" style="width: 100%;">
 | 
			
		||||
              <tbody>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">服务器名称</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.sys">{{ server.sys.computerName }}</div></td>
 | 
			
		||||
                  <td><div class="cell">操作系统</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.sys">{{ server.sys.osName }}</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">服务器IP</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.sys">{{ server.sys.computerIp }}</div></td>
 | 
			
		||||
                  <td><div class="cell">系统架构</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.sys">{{ server.sys.osArch }}</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-col>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- 监控日志保存时间 -->
 | 
			
		||||
      <div class="monitor-log-save-time same-block">
 | 
			
		||||
        保存天数:
 | 
			
		||||
        <el-input v-model="monitorLogSavingDays" class=" same-block" style="width: 120px;"></el-input>
 | 
			
		||||
        <el-button type="primary"
 | 
			
		||||
                   class="same-block"
 | 
			
		||||
                   title="只有提交更改才会生效"
 | 
			
		||||
                   @click="updateMonitorStatusSettingsInfo"
 | 
			
		||||
        >更改
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <!-- 清空记录 -->
 | 
			
		||||
      <div class="clean-monitor-log same-block">
 | 
			
		||||
        <el-button class="same-block"
 | 
			
		||||
                   type="warning"
 | 
			
		||||
                   title="清空所有监控记录"
 | 
			
		||||
                   @click="cleanMonitorLogsInfo"
 | 
			
		||||
        >清空记录
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
      <el-col :span="24" class="card-box">
 | 
			
		||||
        <el-card>
 | 
			
		||||
          <div slot="header">
 | 
			
		||||
            <span>Java虚拟机信息</span>
 | 
			
		||||
    <div class="server-monitor-top">
 | 
			
		||||
      <!-- 左侧服务器信息 -->
 | 
			
		||||
      <el-card class="box-card server-information">
 | 
			
		||||
        <div slot="header" class="clearfix">
 | 
			
		||||
          <div class="server-info-item">服务器</div>
 | 
			
		||||
          <el-select filterable
 | 
			
		||||
                     :value="currentServerName"
 | 
			
		||||
                     class="server-info-item"
 | 
			
		||||
                     placeholder="请选择服务器"
 | 
			
		||||
                     @change="chooseServerInfo"
 | 
			
		||||
          >
 | 
			
		||||
            <el-option
 | 
			
		||||
              v-for="(item,index) in allServerInfo"
 | 
			
		||||
              :key="item.id"
 | 
			
		||||
              :label="item.name || item.ip"
 | 
			
		||||
              :value="index"
 | 
			
		||||
            >
 | 
			
		||||
            </el-option>
 | 
			
		||||
          </el-select>
 | 
			
		||||
          <el-button type="primary"
 | 
			
		||||
                     class="server-info-item"
 | 
			
		||||
                     title="只有提交更改才会生效"
 | 
			
		||||
                     @click="updateServerInfo"
 | 
			
		||||
          >更改
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="server-info-detail">
 | 
			
		||||
          <div v-for="(key,index) in currentServerInfoKeys" :key="index" class="server-info-detail-line text item">
 | 
			
		||||
            <div class="server-info-detail-item">
 | 
			
		||||
              <div style="width: 30%;display: inline-block;">{{ SERVER_KEY_TO_NAME_MAPPING[key] }}:</div>
 | 
			
		||||
              <div v-if="CHANGEABLE_SERVER_FIELDS.indexOf(key) > -1" style="display: inline-block;">
 | 
			
		||||
                <el-input style="display: inline-block; width: 90%;" v-model="currentServer[key]"></el-input>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div v-else style="display: inline-block; "> {{ currentServer[key] }}</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="el-table el-table--enable-row-hover el-table--medium">
 | 
			
		||||
            <table cellspacing="0" style="width: 100%;">
 | 
			
		||||
              <tbody>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">Java名称</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.name }}</div></td>
 | 
			
		||||
                  <td><div class="cell">Java版本</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.version }}</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td><div class="cell">启动时间</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.startTime }}</div></td>
 | 
			
		||||
                  <td><div class="cell">运行时长</div></td>
 | 
			
		||||
                  <td><div class="cell" v-if="server.jvm">{{ server.jvm.runTime }}</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td colspan="1"><div class="cell">安装路径</div></td>
 | 
			
		||||
                  <td colspan="3"><div class="cell" v-if="server.jvm">{{ server.jvm.home }}</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <td colspan="1"><div class="cell">项目路径</div></td>
 | 
			
		||||
                  <td colspan="3"><div class="cell" v-if="server.sys">{{ server.sys.userDir }}</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-col>
 | 
			
		||||
        </div>
 | 
			
		||||
      </el-card>
 | 
			
		||||
 | 
			
		||||
      <el-col :span="24" class="card-box">
 | 
			
		||||
        <el-card>
 | 
			
		||||
          <div slot="header">
 | 
			
		||||
            <span>磁盘状态</span>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="el-table el-table--enable-row-hover el-table--medium">
 | 
			
		||||
            <table cellspacing="0" style="width: 100%;">
 | 
			
		||||
              <thead>
 | 
			
		||||
                <tr>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">盘符路径</div></th>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">文件系统</div></th>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">盘符类型</div></th>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">总大小</div></th>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">可用大小</div></th>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">已用大小</div></th>
 | 
			
		||||
                  <th class="is-leaf"><div class="cell">已用百分比</div></th>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </thead>
 | 
			
		||||
              <tbody v-if="server.sysFiles">
 | 
			
		||||
                <tr v-for="sysFile in server.sysFiles">
 | 
			
		||||
                  <td><div class="cell">{{ sysFile.dirName }}</div></td>
 | 
			
		||||
                  <td><div class="cell">{{ sysFile.sysTypeName }}</div></td>
 | 
			
		||||
                  <td><div class="cell">{{ sysFile.typeName }}</div></td>
 | 
			
		||||
                  <td><div class="cell">{{ sysFile.total }}</div></td>
 | 
			
		||||
                  <td><div class="cell">{{ sysFile.free }}</div></td>
 | 
			
		||||
                  <td><div class="cell">{{ sysFile.used }}</div></td>
 | 
			
		||||
                  <td><div class="cell" :class="{'text-danger': sysFile.usage > 80}">{{ sysFile.usage }}%</div></td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              </tbody>
 | 
			
		||||
            </table>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-card>
 | 
			
		||||
      </el-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
      <!-- 右侧仪表盘 -->
 | 
			
		||||
      <el-card class="box-card information-instrument-panel"
 | 
			
		||||
               v-for="(key, index)  of Object.keys(instrumentBoardData)"
 | 
			
		||||
               :key="`${index}-${key}`">
 | 
			
		||||
        <instrument-board
 | 
			
		||||
          :show-top-title="true"
 | 
			
		||||
          :show-sub-title="true"
 | 
			
		||||
          :ring-graph-key="key"
 | 
			
		||||
          :instrument-board-data="instrumentBoardData[key]"
 | 
			
		||||
          :top-title-key-to-name-mapping="INSTRUMENT_BOARD_KEY_TO_NAME_MAPPING"
 | 
			
		||||
        ></instrument-board>
 | 
			
		||||
      </el-card>
 | 
			
		||||
    </div>
 | 
			
		||||
    <!--  下方折线图  -->
 | 
			
		||||
    <div class="server-monitor-bottom">
 | 
			
		||||
      <!-- 折线图 -->
 | 
			
		||||
      <el-card class="box-card server-monitor-line-chart" v-for="(key, index) in Object.keys(lineChartData).slice(0,2)"
 | 
			
		||||
               :key="`${index}-${key}`">
 | 
			
		||||
        <line-chart :line-chart-key="key"
 | 
			
		||||
                    :server-info="currentServer"
 | 
			
		||||
                    :chart-title="CHART_KEY_NAME_MAPPING[key]"
 | 
			
		||||
                    :chart-data="lineChartData[key]"
 | 
			
		||||
                    :chart-time="lineChartData['datetime']"
 | 
			
		||||
        ></line-chart>
 | 
			
		||||
      </el-card>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
import { getServer } from "@/api/vadmin/monitor/server";
 | 
			
		||||
import {
 | 
			
		||||
  cleanMonitorLog,
 | 
			
		||||
  getMonitorLogs,
 | 
			
		||||
  getMonitorStatusInfo,
 | 
			
		||||
  getServerLatestLog,
 | 
			
		||||
  getServerList,
 | 
			
		||||
  updateMonitorStatusInfo,
 | 
			
		||||
  updateServerInfo
 | 
			
		||||
} from '@/api/vadmin/monitor/server'
 | 
			
		||||
import InstrumentBoard from '@/views/vadmin/monitor/server/components/InstrumentBoard'
 | 
			
		||||
import LineChart from '@/views/vadmin/monitor/server/components/LineChart'
 | 
			
		||||
import moment from 'moment'
 | 
			
		||||
 | 
			
		||||
const debounce = require('lodash/debounce')
 | 
			
		||||
 | 
			
		||||
// 要展示的信息,key -> name
 | 
			
		||||
const SERVER_KEY_TO_NAME_MAPPING = {
 | 
			
		||||
  ip: '服务器IP',
 | 
			
		||||
  name: '服务器名称',
 | 
			
		||||
  os: '操作系统',
 | 
			
		||||
  remark: '备注'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 更新频率类型映射
 | 
			
		||||
const INTERVAL_ID_TO_TYPE_MAPPING = {
 | 
			
		||||
  0: {
 | 
			
		||||
    type: 0,
 | 
			
		||||
    name: '秒',
 | 
			
		||||
    key: 'seconds',
 | 
			
		||||
    second: 1
 | 
			
		||||
  },
 | 
			
		||||
  1: {
 | 
			
		||||
    type: 1,
 | 
			
		||||
    name: '分钟',
 | 
			
		||||
    key: 'minutes',
 | 
			
		||||
    second: 60
 | 
			
		||||
  },
 | 
			
		||||
  2: {
 | 
			
		||||
    type: 2,
 | 
			
		||||
    name: '小时',
 | 
			
		||||
    key: 'hours',
 | 
			
		||||
    second: 60 * 60
 | 
			
		||||
  },
 | 
			
		||||
  3: {
 | 
			
		||||
    type: 3,
 | 
			
		||||
    name: '天',
 | 
			
		||||
    key: 'days',
 | 
			
		||||
    second: 24 * 60 * 60
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
const defaultUpdateInterval = INTERVAL_ID_TO_TYPE_MAPPING['0']
 | 
			
		||||
 | 
			
		||||
// 图表字段映射
 | 
			
		||||
const CHART_KEY_NAME_MAPPING = {
 | 
			
		||||
  cpu: 'CPU',
 | 
			
		||||
  memory: '内存',
 | 
			
		||||
  disk: '磁盘'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 仪表盘字段映射
 | 
			
		||||
const INSTRUMENT_BOARD_KEY_TO_NAME_MAPPING = {
 | 
			
		||||
  cpu: 'CPU使用率',
 | 
			
		||||
  memory: '内存使用率'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 服务器信息可修改字段
 | 
			
		||||
const CHANGEABLE_SERVER_FIELDS = ['name', 'remark']
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  name: "Server",
 | 
			
		||||
  name: 'Server',
 | 
			
		||||
  components: {
 | 
			
		||||
    InstrumentBoard,
 | 
			
		||||
    LineChart
 | 
			
		||||
  },
 | 
			
		||||
  data() {
 | 
			
		||||
    return {
 | 
			
		||||
      SERVER_KEY_TO_NAME_MAPPING,
 | 
			
		||||
      INTERVAL_ID_TO_TYPE_MAPPING,
 | 
			
		||||
      CHART_KEY_NAME_MAPPING,
 | 
			
		||||
      CHANGEABLE_SERVER_FIELDS,
 | 
			
		||||
      INSTRUMENT_BOARD_KEY_TO_NAME_MAPPING,
 | 
			
		||||
      timeRange: [
 | 
			
		||||
        `${moment().format('YYYY-MM-DD')} 00:00:00`,
 | 
			
		||||
        `${moment().format('YYYY-MM-DD')} 23:59:59`
 | 
			
		||||
      ],
 | 
			
		||||
      // 加载层信息
 | 
			
		||||
      loading: [],
 | 
			
		||||
      // 服务器信息
 | 
			
		||||
      server: []
 | 
			
		||||
    };
 | 
			
		||||
      // 所有服务器信息
 | 
			
		||||
      allServerInfo: [],
 | 
			
		||||
      // 当前展示的服务器名称
 | 
			
		||||
      currentServerName: '',
 | 
			
		||||
      // 当前展示的服务器信息
 | 
			
		||||
      currentServer: {},
 | 
			
		||||
      // 当前展示的服务器信息索引,更新服务器信息时用
 | 
			
		||||
      currentServerIndex: 0,
 | 
			
		||||
      // 开启监控控制按钮
 | 
			
		||||
      isOpeningMonitor: false,
 | 
			
		||||
      // 数据更新频率
 | 
			
		||||
      monitorUpdateInterval: 60,
 | 
			
		||||
      // 最小更新频率值
 | 
			
		||||
      minMonitorUpdateInterval: 0,
 | 
			
		||||
      // 更新频率类型
 | 
			
		||||
      intervalType: defaultUpdateInterval.name,
 | 
			
		||||
      // 更新频率单位对应秒
 | 
			
		||||
      intervalTypeUnits: defaultUpdateInterval.second,
 | 
			
		||||
      // 监控日志保存天数
 | 
			
		||||
      monitorLogSavingDays: 30,
 | 
			
		||||
      // 折线图数据
 | 
			
		||||
      lineChartData: {},
 | 
			
		||||
      // 仪表盘数据
 | 
			
		||||
      instrumentBoardData: {}
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    currentServerInfoKeys() {
 | 
			
		||||
      return Object.keys(this.currentServer).filter(key => {
 | 
			
		||||
        if (SERVER_KEY_TO_NAME_MAPPING[key]) {
 | 
			
		||||
          return { [key]: SERVER_KEY_TO_NAME_MAPPING[key] }
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    intervalNameToSecondMapping() {
 | 
			
		||||
      let intervalNameToSecondMapping = {}
 | 
			
		||||
      Object.values(INTERVAL_ID_TO_TYPE_MAPPING).forEach(item => {
 | 
			
		||||
        intervalNameToSecondMapping[item.name] = item.second
 | 
			
		||||
      })
 | 
			
		||||
      return intervalNameToSecondMapping
 | 
			
		||||
    },
 | 
			
		||||
    monitorStatusInfo() {
 | 
			
		||||
      return {
 | 
			
		||||
        enabled: this.isOpeningMonitor ? 1 : 0,
 | 
			
		||||
        save_days: this.monitorLogSavingDays,
 | 
			
		||||
        interval: this.monitorUpdateInterval * this.intervalTypeUnits
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  watch: {
 | 
			
		||||
    currentServer(newServerInfo) {
 | 
			
		||||
      if (newServerInfo) {
 | 
			
		||||
        // 更新最新监控信息
 | 
			
		||||
        this.getServerLatestLogInfo(newServerInfo.id)
 | 
			
		||||
        // 获取监控日志信息
 | 
			
		||||
        this.getCurrentServerMonitorLogs()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  created() {
 | 
			
		||||
    this.getList();
 | 
			
		||||
    this.openLoading();
 | 
			
		||||
    this.openLoading()
 | 
			
		||||
    // 获取所有服务器信息
 | 
			
		||||
    this.getServerList(this.currentServerIndex)
 | 
			
		||||
    // 获取服务器监控频率设置
 | 
			
		||||
    this.getMonitorStatusSettingsInfo()
 | 
			
		||||
  },
 | 
			
		||||
  methods: {
 | 
			
		||||
    /** 查询服务器信息 */
 | 
			
		||||
    getList() {
 | 
			
		||||
      getServer().then(response => {
 | 
			
		||||
        this.server = response.data;
 | 
			
		||||
        this.loading.close();
 | 
			
		||||
      });
 | 
			
		||||
    /** 查询所有服务器基础信息 */
 | 
			
		||||
    getServerList(serverIndex) {
 | 
			
		||||
      getServerList({ pageNum: 'all' }).then(response => {
 | 
			
		||||
        this.allServerInfo = response.data
 | 
			
		||||
        if (this.allServerInfo.length > 0) {
 | 
			
		||||
          this.currentServer = this.allServerInfo[serverIndex || this.currentServerIndex]
 | 
			
		||||
          this.currentServerName = this.currentServer.name || this.currentServer.ip
 | 
			
		||||
        }
 | 
			
		||||
        this.loading.close()
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    /**修改服务器信息*/
 | 
			
		||||
    updateServerInfo() {
 | 
			
		||||
      updateServerInfo(this.currentServer.id, this.currentServer).then(() => {
 | 
			
		||||
        this.msgSuccess('修改服务器信息成功!')
 | 
			
		||||
      }).catch(error => {
 | 
			
		||||
        this.$message.error(error.msg || '提交修改服务器信息出错!')
 | 
			
		||||
      }).finally(() => {
 | 
			
		||||
        this.getServerList()
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    /** 获取服务器最新监控信息 */
 | 
			
		||||
    getServerLatestLogInfo(serverId) {
 | 
			
		||||
      getServerLatestLog(serverId).then(results => {
 | 
			
		||||
        this.instrumentBoardData = results.data
 | 
			
		||||
      }).catch(error => {
 | 
			
		||||
        this.msgError(error.msg || '获取服务器最新监控信息错误!')
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    /** 获取监控日志信息 */
 | 
			
		||||
    getCurrentServerMonitorLogs() {
 | 
			
		||||
      getMonitorLogs(this.currentServer.id, { as: JSON.stringify( { 'create_datetime__range': this.timeRange })}).then(results => {
 | 
			
		||||
        this.lineChartData = results.data
 | 
			
		||||
      }).catch(error => {
 | 
			
		||||
        this.msgError(error.msg || '获取监控日志信息出错误!')
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /** 清除监控日志 */
 | 
			
		||||
    cleanMonitorLogsInfo() {
 | 
			
		||||
      this.$confirm('此操作将删除所有的监控记录,是否继续?', '提示', {
 | 
			
		||||
        confirmButtonText: '确定删除',
 | 
			
		||||
        cancelButtonText: '放弃'
 | 
			
		||||
      }).then(() => {
 | 
			
		||||
        cleanMonitorLog().then(results => {
 | 
			
		||||
          this.msgSuccess('清除记录成功!')
 | 
			
		||||
        }).catch(error => {
 | 
			
		||||
          this.$message.warning(error.msg || '清除记录失败,请重试!')
 | 
			
		||||
        })
 | 
			
		||||
      }).catch(() => {
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    /** 获取监控配置信息 */
 | 
			
		||||
    getMonitorStatusSettingsInfo() {
 | 
			
		||||
      getMonitorStatusInfo().then(results => {
 | 
			
		||||
        let { enabled, interval, save_days } = results.data
 | 
			
		||||
        this.isOpeningMonitor = enabled
 | 
			
		||||
        this.monitorLogSavingDays = parseInt(save_days)
 | 
			
		||||
        this.formatInterval(parseInt(interval))
 | 
			
		||||
      }).catch(error => {
 | 
			
		||||
        this.msgError(error.msg || '获取服务器监控配置信息出错误!')
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    /** 更新监控配置信息 */
 | 
			
		||||
    updateMonitorStatusSettingsInfo() {
 | 
			
		||||
      updateMonitorStatusInfo(this.monitorStatusInfo).then(() => {
 | 
			
		||||
        this.msgSuccess('更新配置成功!')
 | 
			
		||||
      }).catch((error) => {
 | 
			
		||||
        this.msgError(error.msg || '更新服务器监控配置信息出错误!')
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    // 打开加载层
 | 
			
		||||
    openLoading() {
 | 
			
		||||
      this.loading = this.$loading({
 | 
			
		||||
        lock: true,
 | 
			
		||||
        text: "拼命读取中",
 | 
			
		||||
        spinner: "el-icon-loading",
 | 
			
		||||
        background: "rgba(0, 0, 0, 0.7)"
 | 
			
		||||
      });
 | 
			
		||||
        text: '拼命读取中',
 | 
			
		||||
        spinner: 'el-icon-loading',
 | 
			
		||||
        background: 'rgba(0, 0, 0, 0.7)'
 | 
			
		||||
      })
 | 
			
		||||
    },
 | 
			
		||||
    // 选择展示的服务器信息
 | 
			
		||||
    chooseServerInfo(index) {
 | 
			
		||||
      this.currentServerIndex = index
 | 
			
		||||
      this.currentServer = this.allServerInfo[index]
 | 
			
		||||
      this.currentServerName = this.currentServer.name || this.currentServer.ip
 | 
			
		||||
    },
 | 
			
		||||
    // 更改更新频率(周期)数值
 | 
			
		||||
    handleIntervalChange: debounce(function(value) {
 | 
			
		||||
      this.monitorUpdateInterval = value
 | 
			
		||||
    }, 500),
 | 
			
		||||
    // 选择更新频率(周期) 单位
 | 
			
		||||
    selectIntervalType(value) {
 | 
			
		||||
      this.intervalType = value
 | 
			
		||||
      this.intervalTypeUnits = this.intervalNameToSecondMapping[value]
 | 
			
		||||
    },
 | 
			
		||||
    // 修改监控状态
 | 
			
		||||
    changeMonitorStatus(value) {
 | 
			
		||||
      this.isOpeningMonitor = value
 | 
			
		||||
    },
 | 
			
		||||
    // 监控周期时间转换
 | 
			
		||||
    formatInterval(intervalTime) {
 | 
			
		||||
      let biggerInterval = 0
 | 
			
		||||
      for (let interval of Object.values(INTERVAL_ID_TO_TYPE_MAPPING)) {
 | 
			
		||||
        if (interval.second > biggerInterval && interval.second < intervalTime) {
 | 
			
		||||
          biggerInterval = interval.second
 | 
			
		||||
          this.monitorUpdateInterval = intervalTime / interval.second
 | 
			
		||||
          this.intervalType = interval.name
 | 
			
		||||
          this.intervalTypeUnits = interval.second
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
<style scoped>
 | 
			
		||||
.el-button--medium {
 | 
			
		||||
  margin: 2px;
 | 
			
		||||
  padding: 10px 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-monitor-top {
 | 
			
		||||
  padding: 10px 10px;
 | 
			
		||||
  text-align: justify-all;
 | 
			
		||||
  overflow-x: scroll;
 | 
			
		||||
  display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-monitor-bottom {
 | 
			
		||||
  text-align: left;
 | 
			
		||||
  overflow-x: scroll;
 | 
			
		||||
  display: flex;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-information {
 | 
			
		||||
  width: 20%;
 | 
			
		||||
  min-width: 400px;
 | 
			
		||||
  min-height: 300px;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.information-instrument-panel {
 | 
			
		||||
  width: 20%;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  min-height: 300px;
 | 
			
		||||
  min-width: 300px;
 | 
			
		||||
  margin: 0 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-item {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  margin: 0 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-detail {
 | 
			
		||||
  min-height: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-detail-line {
 | 
			
		||||
  margin: 5px;
 | 
			
		||||
  min-height: 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-info-detail-item {
 | 
			
		||||
  text-align: justify;
 | 
			
		||||
  line-height: 40px;
 | 
			
		||||
  margin: 4px 0;
 | 
			
		||||
  overflow: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.server-monitor-control {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 60px;
 | 
			
		||||
  line-height: 60px;
 | 
			
		||||
  padding: 0 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.monitor-update-interval {
 | 
			
		||||
  margin: 0 20px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.same-block {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.monitor-update-interval-blank {
 | 
			
		||||
  width: 100px;
 | 
			
		||||
  margin: 0 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.monitor-update-interval-unit {
 | 
			
		||||
  width: 80px;
 | 
			
		||||
  margin: 0 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.monitor-log-save-time {
 | 
			
		||||
  width: 280px;
 | 
			
		||||
  margin: 0 2px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.clean-monitor-log {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
.server-monitor-line-chart {
 | 
			
		||||
  height: 400px;
 | 
			
		||||
  width: 45%;
 | 
			
		||||
  min-width: 500px;
 | 
			
		||||
  margin: 10px;
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  overflow-x: scroll;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -161,7 +161,7 @@
 | 
			
		|||
                  <i class="el-icon-arrow-down el-icon--right"></i>
 | 
			
		||||
                </span>
 | 
			
		||||
                  <el-dropdown-menu slot="dropdown">
 | 
			
		||||
                    <el-dropdown-item v-for="(role,index) in scope.row.role" :key="index">{{role.roleName}}</el-dropdown-item>
 | 
			
		||||
                    <el-dropdown-item v-for="role in scope.row.role">{{role.roleName}}</el-dropdown-item>
 | 
			
		||||
                  </el-dropdown-menu>
 | 
			
		||||
                </el-dropdown>
 | 
			
		||||
              </template>
 | 
			
		||||
| 
						 | 
				
			
			@ -576,7 +576,7 @@
 | 
			
		|||
          password: undefined,
 | 
			
		||||
          mobile: undefined,
 | 
			
		||||
          email: undefined,
 | 
			
		||||
          gender: this.selectDictDefault(this.sexOptions),
 | 
			
		||||
          gender: undefined,
 | 
			
		||||
          is_active: false,
 | 
			
		||||
          remark: undefined,
 | 
			
		||||
          postIds: [],
 | 
			
		||||
| 
						 | 
				
			
			@ -648,7 +648,6 @@
 | 
			
		|||
        this.$refs['form'].validate(valid => {
 | 
			
		||||
          if (valid) {
 | 
			
		||||
            if (this.form.id != undefined) {
 | 
			
		||||
              this.form.creator = undefined
 | 
			
		||||
              updateUser(this.form).then(response => {
 | 
			
		||||
                this.msgSuccess('修改成功')
 | 
			
		||||
                this.open = false
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -117,7 +117,7 @@
 | 
			
		|||
      <el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
 | 
			
		||||
      <el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
 | 
			
		||||
        <template slot-scope="scope">
 | 
			
		||||
          <router-link :to="'/dict/type/data/' + scope.row.id" class="link-type">
 | 
			
		||||
          <router-link :to="hasPermi(['system:dict:type:get']) ?'/dict/type/data/' + scope.row.id :'#'" class="link-type">
 | 
			
		||||
            <span>{{ scope.row.dictType }}</span>
 | 
			
		||||
          </router-link>
 | 
			
		||||
        </template>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue