├── LICENSE ├── README.md ├── backend ├── account │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── category │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── consumers.py │ ├── filters.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── permissions.py │ ├── routing.py │ ├── serializers.py │ ├── ssh.py │ ├── ssh │ │ ├── __init__.py │ │ ├── ssh_connection.py │ │ ├── ssh_operation.py │ │ ├── webssh_connection.py │ │ └── webssh_consumers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── history │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── handlers.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── logs │ └── .gitignore ├── manage.py ├── open-cmdb │ ├── __init__.py │ ├── gunicorn_config.py │ ├── settings.py │ ├── urls.py │ ├── uwsgi.ini │ └── wsgi.py ├── requirements.txt ├── scripts │ └── sys_info ├── sql │ └── user.json └── utils │ ├── __init__.py │ ├── basefilters.py │ ├── basemodels.py │ ├── baseviews.py │ ├── collect_info.py │ ├── ldaptools.py │ ├── permissions.py │ ├── unitaryauth.py │ └── wrappers.py ├── frontend ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .travis.yml ├── LICENSE ├── README.md ├── cypress.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── api │ │ ├── account │ │ │ ├── groups.js │ │ │ └── users.js │ │ ├── category │ │ │ ├── businesslines.js │ │ │ ├── idcs.js │ │ │ ├── projects.js │ │ │ ├── racks.js │ │ │ ├── servers.js │ │ │ └── sshusers.js │ │ ├── dashboard │ │ │ └── dashboard_data.js │ │ ├── data.js │ │ ├── history │ │ │ └── histories.js │ │ ├── login.js │ │ ├── routers.js │ │ └── user.js │ ├── assets │ │ ├── icons │ │ │ ├── iconfont.css │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.svg │ │ │ ├── iconfont.ttf │ │ │ └── iconfont.woff │ │ └── images │ │ │ ├── error-page │ │ │ ├── error-401.svg │ │ │ ├── error-404.svg │ │ │ └── error-500.svg │ │ │ ├── icon-qr-qq-wechat.png │ │ │ ├── icon-social-bilibili.svg │ │ │ ├── icon-social-juejin.svg │ │ │ ├── icon-social-twitter.svg │ │ │ ├── icon-social-zhihu.svg │ │ │ ├── login-bg.jpg │ │ │ ├── logo-min.jpg │ │ │ ├── logo.jpg │ │ │ └── talkingdata.png │ ├── components │ │ ├── charts │ │ │ ├── bar.vue │ │ │ ├── index.js │ │ │ ├── pie.vue │ │ │ └── theme.json │ │ ├── common-icon │ │ │ ├── common-icon.vue │ │ │ └── index.js │ │ ├── common │ │ │ ├── common.less │ │ │ └── util.js │ │ ├── count-to │ │ │ ├── count-to.vue │ │ │ ├── index.js │ │ │ └── index.less │ │ ├── cropper │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── index.vue │ │ ├── drag-drawer │ │ │ ├── drag-drawer-trigger.vue │ │ │ ├── drag-drawer.vue │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── mixin.js │ │ ├── drag-list │ │ │ ├── drag-list.vue │ │ │ └── index.js │ │ ├── editor │ │ │ ├── editor.vue │ │ │ └── index.js │ │ ├── icons │ │ │ ├── icons.vue │ │ │ └── index.js │ │ ├── info-card │ │ │ ├── index.js │ │ │ └── infor-card.vue │ │ ├── login-form │ │ │ ├── index.js │ │ │ └── login-form.vue │ │ ├── main │ │ │ ├── components │ │ │ │ ├── a-back-top │ │ │ │ │ ├── index.js │ │ │ │ │ └── index.vue │ │ │ │ ├── error-store │ │ │ │ │ ├── error-store.vue │ │ │ │ │ └── index.js │ │ │ │ ├── fullscreen │ │ │ │ │ ├── fullscreen.vue │ │ │ │ │ └── index.js │ │ │ │ ├── header-bar │ │ │ │ │ ├── custom-bread-crumb │ │ │ │ │ │ ├── custom-bread-crumb.less │ │ │ │ │ │ ├── custom-bread-crumb.vue │ │ │ │ │ │ └── index.js │ │ │ │ │ ├── header-bar.less │ │ │ │ │ ├── header-bar.vue │ │ │ │ │ ├── index.js │ │ │ │ │ └── sider-trigger │ │ │ │ │ │ ├── index.js │ │ │ │ │ │ ├── sider-trigger.less │ │ │ │ │ │ └── sider-trigger.vue │ │ │ │ ├── language │ │ │ │ │ ├── index.js │ │ │ │ │ └── language.vue │ │ │ │ ├── side-menu │ │ │ │ │ ├── collapsed-menu.vue │ │ │ │ │ ├── index.js │ │ │ │ │ ├── item-mixin.js │ │ │ │ │ ├── mixin.js │ │ │ │ │ ├── side-menu-item.vue │ │ │ │ │ ├── side-menu.less │ │ │ │ │ └── side-menu.vue │ │ │ │ ├── tags-nav │ │ │ │ │ ├── index.js │ │ │ │ │ ├── tags-nav.less │ │ │ │ │ └── tags-nav.vue │ │ │ │ └── user │ │ │ │ │ ├── index.js │ │ │ │ │ ├── user.less │ │ │ │ │ └── user.vue │ │ │ ├── index.js │ │ │ ├── main.less │ │ │ └── main.vue │ │ ├── markdown │ │ │ ├── index.js │ │ │ └── markdown.vue │ │ ├── parent-view │ │ │ ├── index.js │ │ │ └── parent-view.vue │ │ ├── paste-editor │ │ │ ├── index.js │ │ │ ├── paste-editor.less │ │ │ ├── paste-editor.vue │ │ │ └── plugins │ │ │ │ └── placeholder.js │ │ ├── split-pane │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── split.vue │ │ │ └── trigger.vue │ │ ├── tables │ │ │ ├── edit.vue │ │ │ ├── handle-btns.js │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ └── tables.vue │ │ └── tree-select │ │ │ ├── index.js │ │ │ ├── tree-select-tree.vue │ │ │ └── tree-select.vue │ ├── config │ │ └── index.js │ ├── directive │ │ ├── directives.js │ │ ├── index.js │ │ └── module │ │ │ ├── clipboard.js │ │ │ └── draggable.js │ ├── index.less │ ├── libs │ │ ├── api.request.js │ │ ├── axios.js │ │ ├── excel.js │ │ ├── render-dom.js │ │ ├── tools.js │ │ ├── util.js │ │ └── view │ │ │ └── common.js │ ├── locale │ │ ├── index.js │ │ └── lang │ │ │ ├── en-US.js │ │ │ ├── zh-CN.js │ │ │ └── zh-TW.js │ ├── main.js │ ├── mock │ │ ├── data.js │ │ ├── data │ │ │ ├── org-data.js │ │ │ └── tree-select.js │ │ ├── index.js │ │ ├── login.js │ │ └── user.js │ ├── plugin │ │ ├── error-store │ │ │ └── index.js │ │ └── index.js │ ├── router │ │ ├── before-close.js │ │ ├── index.js │ │ └── routers.js │ ├── static │ │ └── base.css │ ├── store │ │ ├── index.js │ │ └── module │ │ │ ├── app.js │ │ │ └── user.js │ └── view │ │ ├── argu-page │ │ ├── params.vue │ │ └── query.vue │ │ ├── components │ │ ├── count-to │ │ │ └── count-to.vue │ │ ├── cropper │ │ │ └── cropper.vue │ │ ├── drag-drawer │ │ │ └── index.vue │ │ ├── drag-list │ │ │ └── drag-list.vue │ │ ├── editor │ │ │ └── editor.vue │ │ ├── icons │ │ │ └── icons.vue │ │ ├── markdown │ │ │ └── markdown.vue │ │ ├── org-tree │ │ │ ├── components │ │ │ │ ├── org-view.vue │ │ │ │ └── zoom-controller.vue │ │ │ ├── index.less │ │ │ └── index.vue │ │ ├── public │ │ │ └── copyright.vue │ │ ├── split-pane │ │ │ └── split-pane.vue │ │ ├── tables │ │ │ └── tables.vue │ │ ├── tree-select │ │ │ └── index.vue │ │ └── tree-table │ │ │ └── index.vue │ │ ├── directive │ │ └── directive.vue │ │ ├── error-page │ │ ├── 401.vue │ │ ├── 404.vue │ │ ├── 500.vue │ │ ├── back-btn-group.vue │ │ ├── error-content.vue │ │ └── error.less │ │ ├── error-store │ │ └── error-store.vue │ │ ├── excel │ │ ├── common.less │ │ ├── export-excel.vue │ │ └── upload-excel.vue │ │ ├── i18n │ │ └── i18n-page.vue │ │ ├── join-page.vue │ │ ├── login │ │ ├── login.less │ │ └── login.vue │ │ ├── multilevel │ │ ├── level-2-1.vue │ │ ├── level-2-2 │ │ │ ├── level-2-2-1.vue │ │ │ └── level-2-2-2.vue │ │ └── level-2-3.vue │ │ ├── projects │ │ ├── account │ │ │ ├── groups.vue │ │ │ └── users.vue │ │ ├── category │ │ │ ├── Console.vue │ │ │ ├── Xterm.js │ │ │ ├── businesslines.vue │ │ │ ├── idc_detail.vue │ │ │ ├── idcs.vue │ │ │ ├── project_detail.vue │ │ │ ├── projects.vue │ │ │ ├── rack_detail.vue │ │ │ ├── racks.vue │ │ │ ├── server_detail.vue │ │ │ ├── servers.vue │ │ │ ├── sshusers.vue │ │ │ └── webssh_detail.vue │ │ └── history │ │ │ ├── histories.vue │ │ │ └── history_detail.vue │ │ ├── single-page │ │ ├── error-logger.vue │ │ ├── home │ │ │ ├── example.vue │ │ │ ├── home.vue │ │ │ └── index.js │ │ └── message │ │ │ └── index.vue │ │ ├── tools-methods │ │ └── tools-methods.vue │ │ └── update │ │ ├── update-paste.vue │ │ └── update-table.vue ├── tests │ ├── e2e │ │ ├── .eslintrc │ │ ├── plugins │ │ │ └── index.js │ │ ├── specs │ │ │ └── test.js │ │ └── support │ │ │ ├── commands.js │ │ │ └── index.js │ └── unit │ │ ├── .eslintrc.js │ │ └── HelloWorld.spec.js └── vue.config.js └── images ├── dashboard.png ├── idc-detail.png ├── idc-list.png ├── log-detail.png ├── log-list.png ├── server-detail.png ├── server-list.png └── server-ssh.png /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, chen kun 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Django rest framework + vue 的CMDB项目 2 | 3 | 4 | ## 环境 5 | 6 | - Python 3.6 7 | - Django 2.0 8 | - Django Rest Framework 3.8 9 | 10 | - Vue.js 2.9 11 | - iview 3.0 12 | - iview-admin 2.0 13 | 14 | 15 | ## 功能 16 | 17 | - web ssh 18 | - 页面模拟服务器控制台 19 | - CMDB资源管理 20 | - 硬件管理:机房/机柜/设备 21 | - 业务管理:业务线/项目 22 | - 数据自动化:自动抓取服务器信息做集中化存储 23 | - 用户和组 24 | - 报表展示 25 | - 硬件/业务/用户各维度数据图形化 26 | - 定时任务管理 27 | - 对各服务器的定时任务创建/修改 28 | - 批量分发同步 29 | - 任务日志查询 30 | - 历史记录 31 | - 记录用户的各类变更操作 32 | 33 | 34 | ## 部署 35 | - 推荐容器化部署 36 | ```bash 37 | docker pull myide/opencmdb:v1 38 | docker run -itd --name op1 --network host opencmdb:v1 39 | ``` 40 | 41 | 42 | ## 界面 43 | 44 | - Dashboard 45 | 46 | ![image](https://github.com/myide/open-cmdb/blob/master/images/dashboard.png) 47 | 48 | - 机房列表 49 | 50 | ![image](https://github.com/myide/open-cmdb/blob/master/images/idc-list.png) 51 | 52 | - 机房详情 53 | 54 | ![image](https://github.com/myide/open-cmdb/blob/master/images/idc-detail.png) 55 | 56 | - 服务器列表 57 | 58 | ![image](https://github.com/myide/open-cmdb/blob/master/images/server-list.png) 59 | 60 | - 服务器详情 61 | 62 | ![image](https://github.com/myide/open-cmdb/blob/master/images/server-detail.png) 63 | 64 | - 服务器webssh 65 | 66 | ![image](https://github.com/myide/open-cmdb/blob/master/images/server-ssh.png) 67 | 68 | - 操作记录列表 69 | 70 | ![image](https://github.com/myide/open-cmdb/blob/master/images/log-list.png) 71 | 72 | - 操作记录详情 73 | 74 | ![image](https://github.com/myide/open-cmdb/blob/master/images/log-detail.png) 75 | 76 | 77 | ## 交流学习 78 | - QQ群 630791951 79 | -------------------------------------------------------------------------------- /backend/account/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/backend/account/__init__.py -------------------------------------------------------------------------------- /backend/account/admin.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.contrib import admin 5 | 6 | from .models import User 7 | 8 | # Register your models here. 9 | 10 | 11 | class UserAdmin(admin.ModelAdmin): 12 | list_display = ( 13 | 'username', 14 | 'email', 15 | 'role', 16 | 'remark', 17 | ) 18 | 19 | admin.site.register(User, UserAdmin) 20 | -------------------------------------------------------------------------------- /backend/account/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.apps import AppConfig 5 | 6 | 7 | class AccountConfig(AppConfig): 8 | name = 'account' 9 | -------------------------------------------------------------------------------- /backend/account/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/backend/account/migrations/__init__.py -------------------------------------------------------------------------------- /backend/account/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.contrib.auth.models import AbstractUser 3 | from django.db import models 4 | 5 | 6 | class User(AbstractUser): 7 | ROLES = ( 8 | ('developer_supremo', u'总监'), 9 | ('developer_manager', u'经理'), 10 | ('developer', u'研发'), 11 | ) 12 | role = models.CharField(max_length=32, default='developer', choices=ROLES) 13 | remark = models.CharField(max_length=128, default='', blank=True) 14 | 15 | class Meta: 16 | ordering = ['-id'] 17 | verbose_name_plural = u'用户' 18 | 19 | def __unicode__(self): 20 | return self.username 21 | -------------------------------------------------------------------------------- /backend/account/serializers.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from collections import OrderedDict 3 | 4 | from django.contrib.auth.models import Group 5 | from rest_framework import serializers 6 | 7 | from .models import User 8 | 9 | 10 | class UserSerializer(serializers.ModelSerializer): 11 | 12 | class Meta: 13 | model = User 14 | exclude = ['user_permissions'] 15 | 16 | def to_representation(self, instance): 17 | ret = super(UserSerializer, self).to_representation(instance) 18 | if not isinstance(instance, OrderedDict): 19 | group_instance = instance.groups.first() 20 | groups = {'id': group_instance.id, 'name': group_instance.name} if group_instance else {} 21 | ret['groups'] = groups 22 | return ret 23 | 24 | def create(self, validated_data): 25 | instance = super(UserSerializer, self).create(validated_data) 26 | instance.set_password(validated_data['password']) 27 | instance.save() 28 | return instance 29 | 30 | def update(self, instance, validated_data): 31 | password = validated_data.pop('password', None) 32 | if instance.password != password: 33 | instance.set_password(password) 34 | return super(UserSerializer, self).update(instance, validated_data) 35 | 36 | 37 | class GroupSerializer(serializers.ModelSerializer): 38 | 39 | class Meta: 40 | model = Group 41 | exclude = ['permissions'] 42 | 43 | def to_representation(self, instance): 44 | ret = super(GroupSerializer, self).to_representation(instance) 45 | if not isinstance(instance, OrderedDict): 46 | member_set = instance.user_set.all() 47 | members = [{'id': user.id, 'name': user.username, 'role': user.role} for user in member_set] 48 | ret['members'] = members 49 | return ret 50 | -------------------------------------------------------------------------------- /backend/account/tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.test import TestCase 5 | 6 | # Create your tests here. 7 | -------------------------------------------------------------------------------- /backend/account/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import include 3 | from django.conf.urls import url 4 | from rest_framework.routers import DefaultRouter 5 | 6 | from .views import * 7 | 8 | # register的可选参数 base_name: 用来生成urls名字,如果viewset中没有包含queryset, base_name一定要有 9 | 10 | router = DefaultRouter() 11 | router.register(r'groups', GroupViewSet) 12 | router.register(r'users', UserViewSet) 13 | 14 | urlpatterns = [ 15 | url(r'^', include(router.urls)), 16 | url(r'^unitaryauth/$', UnitaryAuthView.as_view()) 17 | ] 18 | -------------------------------------------------------------------------------- /backend/account/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from rest_framework.exceptions import AuthenticationFailed 5 | from rest_framework.response import Response 6 | from rest_framework.views import APIView 7 | 8 | from utils.baseviews import BaseView 9 | from utils.permissions import IsSuperUser 10 | from utils.unitaryauth import UnitaryAuth 11 | from utils.wrappers import permission_admin 12 | 13 | from .serializers import * 14 | 15 | 16 | class GroupViewSet(BaseView): 17 | """ 18 | 系统组CURD 19 | """ 20 | queryset = Group.objects.order_by('-id') 21 | serializer_class = GroupSerializer 22 | permission_classes = [IsSuperUser] 23 | search_fields = ['name'] 24 | 25 | def perform_create(self, serializer): 26 | serializer.create(self.request.data) 27 | 28 | def perform_update(self, serializer): 29 | serializer.update(self.get_object(), self.request.data) 30 | 31 | 32 | class UserViewSet(BaseView): 33 | """ 34 | 系统用户CURD 35 | """ 36 | queryset = User.objects.filter(is_staff=True).order_by('-id') 37 | serializer_class = UserSerializer 38 | permission_classes = [IsSuperUser] 39 | search_fields = ['username'] 40 | 41 | def perform_update(self, serializer): 42 | # 有额外字段,所以绕开了默认的序列化、save那一套;不然的话额外字段会被删除。下同: 43 | serializer.update(self.get_object(), self.request.data) 44 | 45 | @permission_admin 46 | def perform_create(self, serializer): 47 | serializer.create(self.request.data) 48 | 49 | @permission_admin 50 | def perform_destroy(self, instance): 51 | instance.delete() 52 | 53 | 54 | class UnitaryAuthView(UnitaryAuth, APIView): 55 | """ 56 | 接入统一登录 57 | """ 58 | serializer_class = UserSerializer 59 | authentication_classes = () 60 | permission_classes = () 61 | 62 | def post(self, request): 63 | if not self.authenticate: 64 | raise AuthenticationFailed 65 | request.data.update({'is_staff': True}) 66 | serializer = self.serializer_class(data=request.data) 67 | user_query = self.serializer_class.Meta.model.objects.filter(username=request.data.get('username')) 68 | if user_query: 69 | serializer = self.serializer_class(user_query[0], data=request.data) 70 | serializer.is_valid(raise_exception=True) 71 | serializer.save() 72 | return Response(serializer.data) 73 | -------------------------------------------------------------------------------- /backend/category/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/backend/category/__init__.py -------------------------------------------------------------------------------- /backend/category/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import BusinessLine 4 | from .models import Idc 5 | from .models import Project 6 | from .models import Rack 7 | from .models import Server 8 | from .models import SSHUser 9 | 10 | # Register your models here. 11 | 12 | 13 | @admin.register(SSHUser) 14 | class SSHUserAdmin(admin.ModelAdmin): 15 | ordering = ('id',) 16 | 17 | 18 | @admin.register(Idc) 19 | class IdcAdmin(admin.ModelAdmin): 20 | ordering = ('id',) 21 | 22 | 23 | @admin.register(Rack) 24 | class RackAdmin(admin.ModelAdmin): 25 | ordering = ('id',) 26 | 27 | 28 | @admin.register(Server) 29 | class ServerAdmin(admin.ModelAdmin): 30 | ordering = ('id',) 31 | 32 | 33 | @admin.register(BusinessLine) 34 | class BusinessLineAdmin(admin.ModelAdmin): 35 | ordering = ('id',) 36 | 37 | 38 | @admin.register(Project) 39 | class ProjectAdmin(admin.ModelAdmin): 40 | ordering = ('id',) 41 | -------------------------------------------------------------------------------- /backend/category/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CmdbConfig(AppConfig): 5 | name = 'category' 6 | -------------------------------------------------------------------------------- /backend/category/consumers.py: -------------------------------------------------------------------------------- 1 | from channels.generic.websocket import WebsocketConsumer 2 | from django.http.request import QueryDict 3 | from .ssh import SSH 4 | from .models import * 5 | 6 | class WebSSH(WebsocketConsumer): 7 | message = {'status': 0, 'message': None} 8 | 9 | def connect(self): 10 | """ 11 | 打开 websocket 连接, 通过前端传入的参数尝试连接 ssh 主机 12 | :return: 13 | """ 14 | self.accept() 15 | query_string = self.scope.get('query_string') 16 | ssh_args = QueryDict(query_string=query_string, encoding='utf-8') 17 | server_id = ssh_args.get('server_id') 18 | user_id = ssh_args.get('user_id') 19 | server = Server.objects.get(id=server_id) 20 | user = SSHUser.objects.get(id=user_id) 21 | self.ssh = SSH(websocker=self, message=self.message) 22 | ssh_connect_dict = { 23 | 'host': server.ssh_ip, 24 | 'port': server.ssh_port, 25 | 'user': user.name, 26 | 'password': user.password, 27 | 'timeout': 30, 28 | 'pty_width': 500, 29 | 'pty_height': 200, 30 | } 31 | self.ssh.connect(**ssh_connect_dict) 32 | 33 | def disconnect(self, close_code): 34 | self.ssh.close() 35 | 36 | def receive(self, text_data=None, bytes_data=None): 37 | data = text_data 38 | self.ssh.shell(data) 39 | -------------------------------------------------------------------------------- /backend/category/filters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from utils.basefilters import BaseFilter 3 | 4 | from .models import * 5 | 6 | 7 | class IdcFilter(BaseFilter): 8 | 9 | class Meta: 10 | model = Idc 11 | fields = { 12 | 'name': ['icontains'], 13 | 'address': ['icontains'] 14 | } 15 | 16 | 17 | class ServerFilter(BaseFilter): 18 | 19 | class Meta: 20 | model = Server 21 | fields = { 22 | 'name': ['icontains'], 23 | 'ssh_ip': ['icontains'] 24 | } 25 | -------------------------------------------------------------------------------- /backend/category/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/backend/category/migrations/__init__.py -------------------------------------------------------------------------------- /backend/category/permissions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from rest_framework import permissions 3 | 4 | 5 | class ServerPermission(permissions.BasePermission): 6 | 7 | def has_object_permission(self, request, view, obj): 8 | user = request.user 9 | if user.is_superuser: 10 | return True 11 | return user in obj.users.all() 12 | -------------------------------------------------------------------------------- /backend/category/routing.py: -------------------------------------------------------------------------------- 1 | # In routing.py 2 | from channels.routing import ProtocolTypeRouter 3 | from channels.routing import URLRouter 4 | from django.urls import path 5 | 6 | from category.ssh.webssh_consumers import WebSSH 7 | 8 | application = ProtocolTypeRouter({ 9 | "websocket": URLRouter([ 10 | path('webssh/', WebSSH), 11 | ]), 12 | 13 | }) 14 | -------------------------------------------------------------------------------- /backend/category/ssh.py: -------------------------------------------------------------------------------- 1 | import paramiko 2 | import socket 3 | import json 4 | from threading import Thread 5 | 6 | 7 | class SSH: 8 | def __init__(self, websocker, message): 9 | self.websocker = websocker 10 | self.message = message 11 | 12 | def connect(self, host, user, password=None, port=22, timeout=30, 13 | term='xterm', pty_width=80, pty_height=24): 14 | try: 15 | ssh_client = paramiko.SSHClient() 16 | ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 17 | ssh_client.connect(username=user, password=password, hostname=host, port=port, timeout=timeout) 18 | transport = ssh_client.get_transport() 19 | self.channel = transport.open_session() 20 | self.channel.get_pty(term=term, width=pty_width, height=pty_height) 21 | self.channel.invoke_shell() 22 | 23 | for i in range(2): 24 | recv = self.channel.recv(1024).decode('utf-8') 25 | self.websocker.send(recv) 26 | except socket.timeout: 27 | message= 'ssh 连接超时' 28 | self.websocker.send(message) 29 | self.close() 30 | except: 31 | self.close() 32 | 33 | def resize_pty(self, cols, rows): 34 | self.channel.resize_pty(width=cols, height=rows) 35 | 36 | def django_to_ssh(self, data): 37 | try: 38 | self.channel.send(data) 39 | except: 40 | self.close() 41 | 42 | def websocket_to_django(self, data): 43 | self.channel.send(data) 44 | try: 45 | while True: 46 | data = self.channel.recv(1024).decode('utf-8') 47 | if not len(data): 48 | return 49 | self.websocker.send(data) 50 | except: 51 | self.close() 52 | 53 | def close(self): 54 | self.message['status'] = 1 55 | self.message['message'] = 'Close Connection' 56 | message = json.dumps(self.message) 57 | self.websocker.send(message) 58 | #self.channel.close() 59 | self.websocker.close() 60 | 61 | def shell(self, data): 62 | Thread(target=self.websocket_to_django, args=(data,)).start() 63 | -------------------------------------------------------------------------------- /backend/category/ssh/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/backend/category/ssh/__init__.py -------------------------------------------------------------------------------- /backend/category/ssh/ssh_connection.py: -------------------------------------------------------------------------------- 1 | import paramiko 2 | from rest_framework.exceptions import ParseError 3 | 4 | 5 | ''' 6 | https://blog.csdn.net/qq_24674131/article/details/95618304 7 | 8 | 免密登陆的用户 9 | 1. 本机到远程做免密 10 | 2. 远程用户加入sudoers,并设置免密sudo 11 | ''' 12 | 13 | 14 | class SSHConnection: 15 | # 初始化连接创建Transport通道 16 | def __init__(self, host='xxx.xxx.xxx.xxx', port=22, user='xxx', pwd='xxxxx', key_file=''): 17 | self.host = host 18 | self.port = port 19 | self.user = user 20 | self.pwd = pwd 21 | self.key_file = key_file 22 | transport = paramiko.Transport((self.host, self.port)) 23 | if self.key_file: 24 | try: 25 | private_key = paramiko.RSAKey.from_private_key_file(self.key_file) 26 | transport.connect(username=self.user, pkey=private_key) 27 | except Exception as e: 28 | raise ParseError(f'用户{self.key_file}免密连接{self.user}@{self.host}:{self.port}失败,') 29 | else: 30 | transport.connect(username=self.user, password=self.pwd) 31 | self.__transport = transport 32 | self.sftp = paramiko.SFTPClient.from_transport(self.__transport) 33 | 34 | # 关闭通道 35 | def close(self): 36 | self.sftp.close() 37 | self.__transport.close() 38 | 39 | # 上传文件到远程主机 40 | def upload(self, local_path, remote_path): 41 | self.sftp.put(local_path, remote_path) 42 | 43 | # 从远程主机下载文件到本地 44 | def download(self, local_path, remote_path): 45 | self.sftp.get(remote_path, local_path) 46 | 47 | # 在远程主机上创建目录 48 | def mkdir(self, target_path, mode='0777'): 49 | self.sftp.mkdir(target_path, mode) 50 | 51 | # 删除远程主机上的目录 52 | def rmdir(self, target_path): 53 | self.sftp.rmdir(target_path) 54 | 55 | # 查看目录下文件以及子目录(如果需要更加细粒度的文件信息建议使用listdir_attr) 56 | def listdir(self, target_path): 57 | return self.sftp.listdir(target_path) 58 | 59 | # 删除文件 60 | def remove(self, target_path): 61 | self.sftp.remove(target_path) 62 | 63 | # 查看目录下文件以及子目录的详细信息(包含内容和参考os.stat返回一个FSTPAttributes对象,对象的具体属性请用__dict__查看) 64 | def listdir_attr(self, target_path): 65 | try: 66 | files = self.sftp.listdir_attr(target_path) 67 | except BaseException as e: 68 | print(e) 69 | return files 70 | 71 | # 获取文件详情 72 | def stat(self, remote_path): 73 | return self.sftp.stat(remote_path) 74 | 75 | # SSHClient输入命令远程操作主机 76 | def cmd(self, command): 77 | ssh = paramiko.SSHClient() 78 | ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy) 79 | ssh._transport = self.__transport 80 | stdin, stdout, stderr = ssh.exec_command(command) 81 | result = stdout.read() 82 | return result.decode('utf8') 83 | -------------------------------------------------------------------------------- /backend/category/ssh/webssh_connection.py: -------------------------------------------------------------------------------- 1 | import json 2 | import socket 3 | from threading import Thread 4 | 5 | import paramiko 6 | 7 | 8 | class SSH: 9 | 10 | def __init__(self, websocket, message): 11 | self.websocket = websocket 12 | self.message = message 13 | self.channel = None 14 | 15 | def connect(self, host, user, password=None, port=22, timeout=30, term='xterm', pty_width=80, pty_height=24, 16 | private_key=''): 17 | try: 18 | ssh_client = paramiko.SSHClient() 19 | ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 20 | if private_key: 21 | pkey = paramiko.RSAKey.from_private_key_file(private_key) 22 | ssh_client.connect(hostname=host, port=port, username=user, pkey=pkey) 23 | else: 24 | ssh_client.connect(username=user, password=password, hostname=host, port=port, timeout=timeout) 25 | transport = ssh_client.get_transport() 26 | self.channel = transport.open_session() 27 | self.channel.get_pty(term=term, width=pty_width, height=pty_height) 28 | self.channel.invoke_shell() 29 | for i in range(2): 30 | recv = self.channel.recv(1024).decode('utf-8') 31 | self.websocket.send(recv) 32 | except socket.timeout: 33 | message = 'ssh 连接超时' 34 | self.websocket.send(message) 35 | self.close() 36 | except Exception as e: 37 | print(e) 38 | self.close() 39 | 40 | def resize_pty(self, cols, rows): 41 | self.channel.resize_pty(width=cols, height=rows) 42 | 43 | def django_to_ssh(self, data): 44 | try: 45 | self.channel.send(data) 46 | except Exception as e: 47 | print(e) 48 | self.close() 49 | 50 | def websocket_to_django(self, data): 51 | self.channel.send(data) 52 | try: 53 | while True: 54 | data = self.channel.recv(1024).decode('utf-8') 55 | if not len(data): 56 | return 57 | self.websocket.send(data) 58 | except Exception as e: 59 | print(e) 60 | self.close() 61 | 62 | def close(self): 63 | self.message['status'] = 1 64 | self.message['message'] = 'Close Connection' 65 | message = json.dumps(self.message) 66 | self.websocket.send(message) 67 | self.websocket.close() 68 | 69 | def shell(self, data): 70 | Thread(target=self.websocket_to_django, args=(data,)).start() 71 | -------------------------------------------------------------------------------- /backend/category/ssh/webssh_consumers.py: -------------------------------------------------------------------------------- 1 | from channels.generic.websocket import WebsocketConsumer 2 | from django.conf import settings 3 | from django.http.request import QueryDict 4 | 5 | from category.models import * 6 | 7 | from .webssh_connection import SSH 8 | 9 | 10 | class WebSSH(WebsocketConsumer): 11 | ssh = '' 12 | message = {'status': 0, 'message': None} 13 | 14 | def connect(self): 15 | """ 16 | 打开 websocket 连接, 通过前端传入的参数尝试连接 ssh 主机 17 | :return: 18 | """ 19 | self.accept() 20 | query_string = self.scope.get('query_string') 21 | ssh_args = QueryDict(query_string=query_string, encoding='utf-8') 22 | server_id = ssh_args.get('server_id') 23 | user_id = ssh_args.get('user_id') 24 | server = Server.objects.get(id=server_id) 25 | user = SSHUser.objects.get(id=user_id) 26 | private_key = settings.KEY_FILE 27 | self.ssh = SSH(websocket=self, message=self.message) 28 | ssh_connect_dict = { 29 | 'host': server.ssh_ip, 30 | 'port': server.ssh_port, 31 | 'user': user.name, 32 | 'password': user.password, 33 | 'timeout': 30, 34 | 'pty_width': 500, 35 | 'pty_height': 200, 36 | 'private_key': private_key 37 | } 38 | self.ssh.connect(**ssh_connect_dict) 39 | 40 | def disconnect(self, close_code): 41 | self.ssh.close() 42 | 43 | def receive(self, text_data=None, bytes_data=None): 44 | data = text_data 45 | self.ssh.shell(data) 46 | -------------------------------------------------------------------------------- /backend/category/tests.py: -------------------------------------------------------------------------------- 1 | # Create your tests here. 2 | -------------------------------------------------------------------------------- /backend/category/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import include 3 | from django.conf.urls import url 4 | from rest_framework.routers import DefaultRouter 5 | 6 | from .views import * 7 | 8 | # register的可选参数 base_name: 用来生成urls名字,如果viewset中没有包含queryset, base_name一定要有 9 | 10 | router = DefaultRouter() 11 | router.register(r'idcs', IdcViewSet) 12 | router.register(r'racks', RackViewSet) 13 | router.register(r'servers', ServerViewSet) 14 | router.register(r'sshusers', SSHUserViewSet) 15 | router.register(r'businesslines', BusinessLineViewSet) 16 | router.register(r'projects', ProjectViewSet) 17 | 18 | urlpatterns = [ 19 | url(r'^', include(router.urls)), 20 | url(r'^api_dashboard/$', APIDashBoardView.as_view()), 21 | url(r'^api_local_ssh_user/$', APILocalSSHUserView.as_view()), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/history/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | class LogType(object): 3 | CRATE_SERVER = '创建服务器' 4 | DELETE_SERVER = '删除服务器' 5 | UPDATE_SERVER = '更新服务器' 6 | UPDATE_CRON = '更新CRON' 7 | SYNC_CRON = '同步CRON' 8 | -------------------------------------------------------------------------------- /backend/history/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import History 4 | 5 | # Register your models here. 6 | 7 | 8 | @admin.register(History) 9 | class HistoryAdmin(admin.ModelAdmin): 10 | ordering = ('id',) 11 | -------------------------------------------------------------------------------- /backend/history/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HistoryConfig(AppConfig): 5 | name = 'history' 6 | -------------------------------------------------------------------------------- /backend/history/handlers.py: -------------------------------------------------------------------------------- 1 | from .models import History 2 | 3 | 4 | def create_history(name, user, instance, before, after, remark=''): 5 | History.objects.create(name=name, user=user, instance=instance, before=str(before), after=str(after), remark=remark) 6 | -------------------------------------------------------------------------------- /backend/history/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/backend/history/migrations/__init__.py -------------------------------------------------------------------------------- /backend/history/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | from account.models import User 4 | from utils.basemodels import BaseModel 5 | 6 | # Create your models here. 7 | 8 | 9 | class History(BaseModel): 10 | user = models.ForeignKey(User, default='', null=True, blank=True, on_delete=models.SET_DEFAULT, verbose_name='用户') 11 | instance = models.CharField(default='', max_length=32, null=True, blank=True, verbose_name='对象') 12 | before = models.TextField(default='', null=True, blank=True, verbose_name='操作前') 13 | after = models.TextField(default='', null=True, blank=True, verbose_name='操作后') 14 | 15 | class Meta: 16 | ordering = ['-id'] 17 | -------------------------------------------------------------------------------- /backend/history/serializers.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from rest_framework import serializers 3 | 4 | from .models import * 5 | 6 | 7 | class HistorySerializer(serializers.ModelSerializer): 8 | user = serializers.SerializerMethodField() 9 | 10 | class Meta: 11 | model = History 12 | fields = '__all__' 13 | 14 | def get_user(self, instance): 15 | return instance.user.username 16 | -------------------------------------------------------------------------------- /backend/history/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/history/urls.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.conf.urls import include 3 | from django.conf.urls import url 4 | from rest_framework.routers import DefaultRouter 5 | 6 | from .views import * 7 | 8 | # register的可选参数 base_name: 用来生成urls名字,如果viewset中没有包含queryset, base_name一定要有 9 | 10 | router = DefaultRouter() 11 | router.register(r'histories', HistoryViewSet) 12 | 13 | urlpatterns = [ 14 | url(r'^', include(router.urls)) 15 | ] 16 | -------------------------------------------------------------------------------- /backend/history/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from utils.baseviews import BaseView 4 | 5 | from .models import * 6 | from .serializers import * 7 | 8 | 9 | class HistoryViewSet(BaseView): 10 | """ 11 | 历史记录 12 | """ 13 | queryset = History.objects.all() 14 | serializer_class = HistorySerializer 15 | search_fields = ['name'] 16 | -------------------------------------------------------------------------------- /backend/logs/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "open-cmdb.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /backend/open-cmdb/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/backend/open-cmdb/__init__.py -------------------------------------------------------------------------------- /backend/open-cmdb/gunicorn_config.py: -------------------------------------------------------------------------------- 1 | # 配置服务器的监听ip和端口 2 | bind = '127.0.0.1:8090' 3 | # 以守护进程方式运行 4 | daemon = True 5 | # worker数量 6 | workers = 2 7 | # 错误日志路径 8 | errorlog = 'logs/gunicorn.error.log' 9 | # 访问日志路径 10 | accesslog = 'logs/gunicorn.access.log' 11 | -------------------------------------------------------------------------------- /backend/open-cmdb/urls.py: -------------------------------------------------------------------------------- 1 | """open-cmdb URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | 17 | from django.contrib import admin 18 | from django.urls import include 19 | from django.urls import path 20 | # swagger pakeage 21 | from rest_framework.schemas import get_schema_view 22 | from rest_framework_jwt.views import obtain_jwt_token 23 | from rest_framework_swagger.renderers import OpenAPIRenderer 24 | from rest_framework_swagger.renderers import SwaggerUIRenderer 25 | 26 | schema_view = get_schema_view(title='Users API', renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer]) 27 | 28 | urlpatterns = [ 29 | path('admin/', admin.site.urls), 30 | path('api/docs/', schema_view), # swagger doc 31 | path('api/api-auth/', include('rest_framework.urls', namespace='rest_framework')), # wagger login 32 | path('api/api-token-auth/', obtain_jwt_token), 33 | path('api/account/', include('account.urls')), 34 | path('api/category/', include('category.urls')), 35 | path('api/history/', include('history.urls')), 36 | ] 37 | -------------------------------------------------------------------------------- /backend/open-cmdb/uwsgi.ini: -------------------------------------------------------------------------------- 1 | [uwsgi] 2 | # 配置服务器的监听ip和端口,让uWSGI作为nginx的支持服务器的话,设置socke就行;如果要让uWSGI作为单独的web-server,用http 3 | http = 127.0.0.1:200 4 | #socket = 127.0.0.1:8090 5 | # 配置项目目录(此处设置为项目的根目录) 6 | chdir = /mnt/open-cmdb 7 | # 配置入口模块 (django的入口函数的模块,即setting同级目录下的wsgi.py) 8 | wsgi-file = open-cmdb/wsgi.py 9 | # 开启master, 将会多开一个管理进程, 管理其他服务进程 10 | master = True 11 | # 服务器开启的进程数量 12 | processes = 2 13 | # 以守护进程方式提供服, 输出信息将会打印到log中 14 | daemonize = wsgi.log 15 | # 服务器进程开启的线程数量 16 | threads = 4 17 | # 退出的时候清空环境变量 18 | vacuum = true 19 | # 进程pid 20 | pidfile = uwsgi.pid -------------------------------------------------------------------------------- /backend/open-cmdb/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for open-cmdb project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "open-cmdb.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | isort==4.3.21 2 | croniter==0.3.34 3 | cryptography==2.9.2 4 | redis==2.10.6 5 | pymysql==0.9.2 6 | celery==3.1.22 7 | celery-with-redis==3.0 8 | uwsgi==2.0.17.1 9 | gunicorn==19.9.0 10 | channels==2.2.0 11 | python-ldap==3.2.0 12 | paramiko==2.7.1 13 | django==2.0.8 14 | django-celery==3.2.2 15 | djangorestframework-bulk==0.2.1 16 | django-guardian==1.4.9 17 | djangorestframework==3.8.2 18 | django-filter==2.0.0 19 | django-rest-swagger==2.1.1 20 | djangorestframework-jwt==1.11.0 21 | django-cors-headers==2.4.0 -------------------------------------------------------------------------------- /backend/scripts/sys_info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/backend/scripts/sys_info -------------------------------------------------------------------------------- /backend/sql/user.json: -------------------------------------------------------------------------------- 1 | [{"model": "account.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$100000$EWyBc3GjXMw3$zNdcwsGbvyk6XXADvb+phcymmR/f/Czd0++IK4pxuiQ=", "last_login": "2020-08-02T14:10:23.005", "is_superuser": true, "username": "admin", "first_name": "", "last_name": "", "email": "", "is_staff": true, "is_active": true, "date_joined": "2020-05-31T22:40:01.616", "role": "developer", "remark": "", "groups": [], "user_permissions": []}}] 2 | -------------------------------------------------------------------------------- /backend/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/backend/utils/__init__.py -------------------------------------------------------------------------------- /backend/utils/basefilters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import django_filters 3 | 4 | 5 | class BaseFilter(django_filters.FilterSet): 6 | sort = django_filters.OrderingFilter(fields=('create_time',)) 7 | 8 | class Meta: 9 | model = None 10 | fields = {} 11 | -------------------------------------------------------------------------------- /backend/utils/basemodels.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django.db import models 3 | 4 | 5 | class BaseModel(models.Model): 6 | ''' 7 | 基础表(抽象类) 8 | ''' 9 | name = models.CharField(default='', null=True, blank=True, max_length=128, verbose_name='名字') 10 | create_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') 11 | update_time = models.DateTimeField(auto_now=True, verbose_name='修改时间') 12 | remark = models.TextField(default='', null=True, blank=True, verbose_name='备注') 13 | 14 | def __str__(self): 15 | return '{}'.format(self.name) 16 | 17 | class Meta: 18 | abstract = True 19 | -------------------------------------------------------------------------------- /backend/utils/baseviews.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from django_filters.rest_framework import DjangoFilterBackend 3 | from rest_framework import filters 4 | from rest_framework import viewsets 5 | from rest_framework.pagination import PageNumberPagination 6 | from rest_framework.permissions import IsAuthenticated 7 | 8 | 9 | class ReturnFormatMixin(object): 10 | 11 | @classmethod 12 | def get_ret(cls): 13 | return {'status': 0, 'msg': '', 'data': {}} 14 | 15 | 16 | class BasePagination(PageNumberPagination): 17 | page_size_query_param = 'pagesize' 18 | page_query_param = 'page' 19 | max_page_size = 1000 20 | 21 | 22 | class DefaultPagination(BasePagination): 23 | page_size = 10 24 | 25 | 26 | class MaxSizePagination(BasePagination): 27 | page_size = 1000 28 | 29 | 30 | class BaseView(viewsets.ModelViewSet): 31 | queryset = None 32 | serializer_class = None 33 | permission_classes = [IsAuthenticated] 34 | # 分页 35 | pagination_class = DefaultPagination 36 | # 搜索 37 | filter_backends = [filters.SearchFilter, DjangoFilterBackend] 38 | search_fields = [] # 用于单一搜索:接收前端的search参数,从多个字段中匹配,或的关系 39 | filter_class = None # 用于综合搜索:可接收前端的多个查询参数,对相应的字段匹配,且的关系 40 | -------------------------------------------------------------------------------- /backend/utils/ldaptools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import ldap 3 | from django.conf import settings 4 | 5 | 6 | class LdapAuth(object): 7 | 8 | locals().update(settings.LDAP) 9 | ret = {} 10 | 11 | def check(self, ldap_user, ldap_password): 12 | uri = "ldap://{}:{}".format(self.host, self.port) 13 | conn = ldap.initialize(uri) 14 | try: 15 | conn.simple_bind_s(ldap_user, ldap_password) 16 | status = 0 17 | except ldap.INVALID_CREDENTIALS: 18 | status = -1 19 | except Exception as e: 20 | status = -2 21 | self.ret["data"] = e 22 | self.ret["status"] = status 23 | return self.ret 24 | -------------------------------------------------------------------------------- /backend/utils/permissions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from rest_framework import permissions 3 | 4 | SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS') 5 | 6 | 7 | class IsSuperUser(permissions.BasePermission): 8 | """ 9 | Allows access only to super users. 10 | """ 11 | def has_permission(self, request, view): 12 | return request.method in SAFE_METHODS or request.user.is_superuser 13 | -------------------------------------------------------------------------------- /backend/utils/unitaryauth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from utils.ldaptools import LdapAuth 3 | 4 | 5 | class UnitaryAuth(object): 6 | 7 | @property 8 | def authenticate(self): 9 | ''' 10 | 1. 请求认证接口, 入参:需要根据接口的定义, 一般是 用户名(username), 密码(password) 11 | 2. 接口认证成功,本方法返回True, 失败返回False 12 | :return: True/False 13 | ''' 14 | data = self.request.data 15 | ldap = LdapAuth() 16 | ret = ldap.check(data.get('username'), data.get('password')) 17 | status = ret['status'] 18 | return True if status == 0 else False 19 | -------------------------------------------------------------------------------- /backend/utils/wrappers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | import time 4 | from functools import wraps 5 | 6 | from django.db import close_old_connections 7 | from rest_framework.exceptions import ParseError 8 | from rest_framework.exceptions import PermissionDenied 9 | 10 | 11 | def permission_admin(func): 12 | @wraps(func) 13 | def wrapper(self, *args, **kwargs): 14 | if self.request.user.is_superuser: 15 | return func(self, *args, **kwargs) 16 | raise PermissionDenied 17 | return wrapper 18 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@vue/app" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/.eslintignore -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | 'extends': [ 4 | 'plugin:vue/essential', 5 | '@vue/standard' 6 | ], 7 | rules: { 8 | // allow async-await 9 | 'generator-star-spacing': 'off', 10 | // allow debugger during development 11 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 12 | 'vue/no-parsing-error': [2, { 13 | 'x-invalid-end-tag': false 14 | }], 15 | 'no-undef': 'off', 16 | 'camelcase': 'off' 17 | }, 18 | parserOptions: { 19 | parser: 'babel-eslint' 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | package-lock.json 6 | 7 | /tests/e2e/videos/ 8 | /tests/e2e/screenshots/ 9 | 10 | # local env files 11 | .env.local 12 | .env.*.local 13 | 14 | # Log files 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | # Editor directories and files 20 | .idea 21 | .vscode 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw* 27 | 28 | build/env.js 29 | -------------------------------------------------------------------------------- /frontend/.postcssrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /frontend/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: stable 3 | script: npm run lint 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /frontend/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 iView 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js" 3 | } 4 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iview-admin", 3 | "version": "2.0.0", 4 | "author": "Lison", 5 | "private": false, 6 | "scripts": { 7 | "dev": "vue-cli-service serve --open", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint", 10 | "test:unit": "vue-cli-service test:unit", 11 | "test:e2e": "vue-cli-service test:e2e" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.18.0", 15 | "clipboard": "^2.0.0", 16 | "codemirror": "^5.38.0", 17 | "countup": "^1.8.2", 18 | "cropperjs": "^1.2.2", 19 | "dayjs": "^1.7.7", 20 | "echarts": "^4.0.4", 21 | "html2canvas": "^1.0.0-alpha.12", 22 | "iview": "^3.2.2", 23 | "iview-area": "^1.5.17", 24 | "js-cookie": "^2.2.0", 25 | "simplemde": "^1.11.2", 26 | "sortablejs": "^1.7.0", 27 | "tree-table-vue": "^1.1.0", 28 | "v-org-tree": "^1.0.6", 29 | "vue": "^2.5.10", 30 | "vue-i18n": "^7.8.0", 31 | "vue-markdown": "^2.2.4", 32 | "vue-router": "^3.0.1", 33 | "vuedraggable": "^2.16.0", 34 | "vuex": "^3.0.1", 35 | "wangeditor": "^3.1.1", 36 | "xlsx": "^0.13.3", 37 | "xterm": "^3.14.5" 38 | }, 39 | "devDependencies": { 40 | "@vue/cli-plugin-babel": "^3.0.1", 41 | "@vue/cli-plugin-eslint": "^3.0.1", 42 | "@vue/cli-plugin-unit-mocha": "^3.0.1", 43 | "@vue/cli-service": "^3.0.1", 44 | "@vue/eslint-config-standard": "^3.0.0-beta.10", 45 | "@vue/test-utils": "^1.0.0-beta.10", 46 | "chai": "^4.1.2", 47 | "eslint-plugin-cypress": "^2.0.1", 48 | "less": "^2.7.3", 49 | "less-loader": "^4.0.5", 50 | "lint-staged": "^6.0.0", 51 | "mockjs": "^1.0.1-beta3", 52 | "vue-template-compiler": "^2.5.13", 53 | "vue2-ace-editor": "^0.0.13" 54 | }, 55 | "browserslist": [ 56 | "> 1%", 57 | "last 2 versions", 58 | "not ie <= 8" 59 | ], 60 | "gitHooks": { 61 | "pre-commit": "lint-staged" 62 | }, 63 | "lint-staged": { 64 | "*.js": [ 65 | "vue-cli-service lint", 66 | "git add" 67 | ], 68 | "*.vue": [ 69 | "vue-cli-service lint", 70 | "git add" 71 | ] 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 28 | -------------------------------------------------------------------------------- /frontend/src/api/account/groups.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const groupUrl = '/api/account/groups/' 4 | 5 | export const GetGroupList = (params) => { 6 | return axios.request({ 7 | url: groupUrl, 8 | method: 'get', 9 | params: params 10 | }) 11 | } 12 | 13 | export const GetGroup = (id) => { 14 | return axios.request({ 15 | url: groupUrl + id + '/', 16 | method: 'get' 17 | }) 18 | } 19 | 20 | export const CreateGroup = (data) => { 21 | return axios.request({ 22 | url: groupUrl, 23 | method: 'post', 24 | data: data 25 | }) 26 | } 27 | 28 | export const UpdateGroup = (id, data) => { 29 | return axios.request({ 30 | url: groupUrl + id + '/', 31 | method: 'put', 32 | data: data 33 | }) 34 | } 35 | 36 | export const DeleteGroup = (id) => { 37 | return axios.request({ 38 | url: groupUrl + id + '/', 39 | method: 'delete' 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/api/account/users.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const userUrl = '/api/account/users/' 4 | 5 | export const GetUserList = (params) => { 6 | return axios.request({ 7 | url: userUrl, 8 | method: 'get', 9 | params: params 10 | }) 11 | } 12 | 13 | export const GetUser = (id) => { 14 | return axios.request({ 15 | url: userUrl + id + '/', 16 | method: 'get' 17 | }) 18 | } 19 | 20 | export const CreateUser = (data) => { 21 | return axios.request({ 22 | url: userUrl, 23 | method: 'post', 24 | data: data 25 | }) 26 | } 27 | 28 | export const UpdateUser = (id, data) => { 29 | return axios.request({ 30 | url: userUrl + id + '/', 31 | method: 'put', 32 | data: data 33 | }) 34 | } 35 | 36 | export const DeleteUser = (id) => { 37 | return axios.request({ 38 | url: userUrl + id + '/', 39 | method: 'delete' 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/api/category/businesslines.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const businessLineUrl = '/api/category/businesslines/' 4 | 5 | export const GetBusinessLineList = (params) => { 6 | return axios.request({ 7 | url: businessLineUrl, 8 | method: 'get', 9 | params: params 10 | }) 11 | } 12 | 13 | export const GetBusinessLine = (id) => { 14 | return axios.request({ 15 | url: businessLineUrl + id + '/', 16 | method: 'get' 17 | }) 18 | } 19 | 20 | export const CreateBusinessLine = (data) => { 21 | return axios.request({ 22 | url: businessLineUrl, 23 | method: 'post', 24 | data: data 25 | }) 26 | } 27 | 28 | export const UpdateBusinessLine = (id, data) => { 29 | return axios.request({ 30 | url: businessLineUrl + id + '/', 31 | method: 'put', 32 | data: data 33 | }) 34 | } 35 | 36 | export const DeleteBusinessLine = (id) => { 37 | return axios.request({ 38 | url: businessLineUrl + id + '/', 39 | method: 'delete' 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/api/category/idcs.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const idcUrl = '/api/category/idcs/' 4 | 5 | export const GetIdcList = (params) => { 6 | return axios.request({ 7 | url: idcUrl, 8 | method: 'get', 9 | params: params 10 | }) 11 | } 12 | 13 | export const GetIdc = (id) => { 14 | return axios.request({ 15 | url: idcUrl + id + '/', 16 | method: 'get' 17 | }) 18 | } 19 | 20 | export const CreateIdc = (data) => { 21 | return axios.request({ 22 | url: idcUrl, 23 | method: 'post', 24 | data: data 25 | }) 26 | } 27 | 28 | export const UpdateIdc = (id, data) => { 29 | return axios.request({ 30 | url: idcUrl + id + '/', 31 | method: 'put', 32 | data: data 33 | }) 34 | } 35 | 36 | export const DeleteIdc = (id) => { 37 | return axios.request({ 38 | url: idcUrl + id + '/', 39 | method: 'delete' 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/api/category/projects.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const projectUrl = '/api/category/projects/' 4 | 5 | export const GetProjectList = (params) => { 6 | return axios.request({ 7 | url: projectUrl, 8 | method: 'get', 9 | params: params 10 | }) 11 | } 12 | 13 | export const GetProject = (id) => { 14 | return axios.request({ 15 | url: projectUrl + id + '/', 16 | method: 'get' 17 | }) 18 | } 19 | 20 | export const CreateProject = (data) => { 21 | return axios.request({ 22 | url: projectUrl, 23 | method: 'post', 24 | data: data 25 | }) 26 | } 27 | 28 | export const UpdateProject = (id, data) => { 29 | return axios.request({ 30 | url: projectUrl + id + '/', 31 | method: 'put', 32 | data: data 33 | }) 34 | } 35 | 36 | export const DeleteProject = (id) => { 37 | return axios.request({ 38 | url: projectUrl + id + '/', 39 | method: 'delete' 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/api/category/racks.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const rackUrl = '/api/category/racks/' 4 | 5 | export const GetRackList = (params) => { 6 | return axios.request({ 7 | url: rackUrl, 8 | method: 'get', 9 | params: params 10 | }) 11 | } 12 | 13 | export const GetRack = (id) => { 14 | return axios.request({ 15 | url: rackUrl + id + '/', 16 | method: 'get' 17 | }) 18 | } 19 | 20 | export const CreateRack = (data) => { 21 | return axios.request({ 22 | url: rackUrl, 23 | method: 'post', 24 | data: data 25 | }) 26 | } 27 | 28 | export const UpdateRack = (id, data) => { 29 | return axios.request({ 30 | url: rackUrl + id + '/', 31 | method: 'put', 32 | data: data 33 | }) 34 | } 35 | 36 | export const DeleteRack = (id) => { 37 | return axios.request({ 38 | url: rackUrl + id + '/', 39 | method: 'delete' 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/api/category/servers.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const serverUrl = '/api/category/servers/' 4 | 5 | export const GetServerList = (params) => { 6 | return axios.request({ 7 | url: serverUrl, 8 | method: 'get', 9 | params: params 10 | }) 11 | } 12 | 13 | export const GetServer = (id) => { 14 | return axios.request({ 15 | url: serverUrl + id + '/', 16 | method: 'get' 17 | }) 18 | } 19 | 20 | export const CreateServer = (data) => { 21 | return axios.request({ 22 | url: serverUrl, 23 | method: 'post', 24 | data: data 25 | }) 26 | } 27 | 28 | export const UpdateServer = (id, data) => { 29 | return axios.request({ 30 | url: serverUrl + id + '/', 31 | method: 'put', 32 | data: data 33 | }) 34 | } 35 | 36 | export const DeleteServer = (id) => { 37 | return axios.request({ 38 | url: serverUrl + id + '/', 39 | method: 'delete' 40 | }) 41 | } 42 | 43 | export const FetchServerInfo = (id) => { 44 | return axios.request({ 45 | url: serverUrl + id + '/info/', 46 | method: 'get' 47 | }) 48 | } 49 | 50 | export const FetchServerCron = (id, data) => { 51 | return axios.request({ 52 | url: serverUrl + id + '/fetch_cron_content/', 53 | method: 'post', 54 | data: data 55 | }) 56 | } 57 | 58 | export const FetchServerCronLog = (id, data) => { 59 | return axios.request({ 60 | url: serverUrl + id + '/fetch_cron_log/', 61 | method: 'post', 62 | data: data 63 | }) 64 | } 65 | 66 | export const UpdateServerCron = (id, data) => { 67 | return axios.request({ 68 | url: serverUrl + id + '/update_cron_file/', 69 | method: 'post', 70 | data: data 71 | }) 72 | } 73 | 74 | export const SyncServerCron = (id, data) => { 75 | return axios.request({ 76 | url: serverUrl + id + '/sync_cron_file/', 77 | method: 'post', 78 | data: data 79 | }) 80 | } 81 | -------------------------------------------------------------------------------- /frontend/src/api/category/sshusers.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const sshUserUrl = '/api/category/sshusers/' 4 | const LocalSSHUserUrl = '/api/category/api_local_ssh_user/' 5 | 6 | export const GetSSHUserList = (params) => { 7 | return axios.request({ 8 | url: sshUserUrl, 9 | method: 'get', 10 | params: params 11 | }) 12 | } 13 | 14 | export const GetSSHUser = (id) => { 15 | return axios.request({ 16 | url: sshUserUrl + id + '/', 17 | method: 'get' 18 | }) 19 | } 20 | 21 | export const CreateSSHUser = (data) => { 22 | return axios.request({ 23 | url: sshUserUrl, 24 | method: 'post', 25 | data: data 26 | }) 27 | } 28 | 29 | export const UpdateSSHUser = (id, data) => { 30 | return axios.request({ 31 | url: sshUserUrl + id + '/', 32 | method: 'put', 33 | data: data 34 | }) 35 | } 36 | 37 | export const DeleteSSHUser = (id) => { 38 | return axios.request({ 39 | url: sshUserUrl + id + '/', 40 | method: 'delete' 41 | }) 42 | } 43 | 44 | export const GetLocalSSHUser = (data) => { 45 | return axios.request({ 46 | url: LocalSSHUserUrl, 47 | method: 'get' 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /frontend/src/api/dashboard/dashboard_data.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const dashBoardUrl = '/api/category/api_dashboard/' 4 | 5 | export const GetDashBoardData = (params) => { 6 | return axios.request({ 7 | url: dashBoardUrl, 8 | method: 'get', 9 | params: params 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/api/data.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | export const getTableData = () => { 4 | return axios.request({ 5 | url: 'get_table_data', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export const getDragList = () => { 11 | return axios.request({ 12 | url: 'get_drag_list', 13 | method: 'get' 14 | }) 15 | } 16 | 17 | export const errorReq = () => { 18 | return axios.request({ 19 | url: 'error_url', 20 | method: 'post' 21 | }) 22 | } 23 | 24 | export const saveErrorLogger = info => { 25 | return axios.request({ 26 | url: 'save_error_logger', 27 | data: info, 28 | method: 'post' 29 | }) 30 | } 31 | 32 | export const uploadImg = formData => { 33 | return axios.request({ 34 | url: 'image/upload', 35 | data: formData 36 | }) 37 | } 38 | 39 | export const getOrgData = () => { 40 | return axios.request({ 41 | url: 'get_org_data', 42 | method: 'get' 43 | }) 44 | } 45 | 46 | export const getTreeSelectData = () => { 47 | return axios.request({ 48 | url: 'get_tree_select_data', 49 | method: 'get' 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/api/history/histories.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | const historyUrl = '/api/history/histories/' 4 | 5 | export const GetHistoryList = (params) => { 6 | return axios.request({ 7 | url: historyUrl, 8 | method: 'get', 9 | params: params 10 | }) 11 | } 12 | 13 | export const GetHistory = (id) => { 14 | return axios.request({ 15 | url: historyUrl + id + '/', 16 | method: 'get' 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/api/login.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | export const Login = (data) => { 4 | return axios.request({ 5 | url: '/api/api-token-auth/', 6 | method: 'post', 7 | data: data 8 | }) 9 | } 10 | 11 | export const UnifiedAuth = (data) => { 12 | return axios.request({ 13 | url: '/api/account/unitaryauth/', 14 | method: 'post', 15 | data: data 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/api/routers.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | export const getRouterReq = (access) => { 4 | return axios.request({ 5 | url: 'get_router', 6 | params: { 7 | access 8 | }, 9 | method: 'get' 10 | }) 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/api/user.js: -------------------------------------------------------------------------------- 1 | import axios from '@/libs/api.request' 2 | 3 | export const login = ({ userName, password }) => { 4 | const data = { 5 | userName, 6 | password 7 | } 8 | return axios.request({ 9 | url: 'login', 10 | data, 11 | method: 'post' 12 | }) 13 | } 14 | 15 | export const getUserInfo = (token) => { 16 | return axios.request({ 17 | url: 'get_info', 18 | params: { 19 | token 20 | }, 21 | method: 'get' 22 | }) 23 | } 24 | 25 | export const logout = (token) => { 26 | return axios.request({ 27 | url: 'logout', 28 | method: 'post' 29 | }) 30 | } 31 | 32 | export const getUnreadCount = () => { 33 | return axios.request({ 34 | url: 'message/count', 35 | method: 'get' 36 | }) 37 | } 38 | 39 | export const getMessage = () => { 40 | return axios.request({ 41 | url: 'message/init', 42 | method: 'get' 43 | }) 44 | } 45 | 46 | export const getContentByMsgId = msg_id => { 47 | return axios.request({ 48 | url: 'message/content', 49 | method: 'get', 50 | params: { 51 | msg_id 52 | } 53 | }) 54 | } 55 | 56 | export const hasRead = msg_id => { 57 | return axios.request({ 58 | url: 'message/has_read', 59 | method: 'post', 60 | data: { 61 | msg_id 62 | } 63 | }) 64 | } 65 | 66 | export const removeReaded = msg_id => { 67 | return axios.request({ 68 | url: 'message/remove_readed', 69 | method: 'post', 70 | data: { 71 | msg_id 72 | } 73 | }) 74 | } 75 | 76 | export const restoreTrash = msg_id => { 77 | return axios.request({ 78 | url: 'message/restore', 79 | method: 'post', 80 | data: { 81 | msg_id 82 | } 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /frontend/src/assets/icons/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/src/assets/icons/iconfont.eot -------------------------------------------------------------------------------- /frontend/src/assets/icons/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/src/assets/icons/iconfont.ttf -------------------------------------------------------------------------------- /frontend/src/assets/icons/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/src/assets/icons/iconfont.woff -------------------------------------------------------------------------------- /frontend/src/assets/images/icon-qr-qq-wechat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/src/assets/images/icon-qr-qq-wechat.png -------------------------------------------------------------------------------- /frontend/src/assets/images/icon-social-juejin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/images/icon-social-zhihu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/images/login-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/src/assets/images/login-bg.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/logo-min.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/src/assets/images/logo-min.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/src/assets/images/logo.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/talkingdata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/frontend/src/assets/images/talkingdata.png -------------------------------------------------------------------------------- /frontend/src/components/charts/bar.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 59 | -------------------------------------------------------------------------------- /frontend/src/components/charts/index.js: -------------------------------------------------------------------------------- 1 | import ChartPie from './pie.vue' 2 | import ChartBar from './bar.vue' 3 | export { ChartPie, ChartBar } 4 | -------------------------------------------------------------------------------- /frontend/src/components/charts/pie.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 71 | -------------------------------------------------------------------------------- /frontend/src/components/common-icon/common-icon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | 40 | 43 | -------------------------------------------------------------------------------- /frontend/src/components/common-icon/index.js: -------------------------------------------------------------------------------- 1 | import CommonIcon from './common-icon.vue' 2 | export default CommonIcon 3 | -------------------------------------------------------------------------------- /frontend/src/components/common/common.less: -------------------------------------------------------------------------------- 1 | .no-select{ 2 | -webkit-touch-callout: none; 3 | -webkit-user-select: none; 4 | -khtml-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | -------------------------------------------------------------------------------- /frontend/src/components/common/util.js: -------------------------------------------------------------------------------- 1 | export const showTitle = (item, vm) => { 2 | return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name) 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/components/count-to/index.js: -------------------------------------------------------------------------------- 1 | import countTo from './count-to.vue' 2 | export default countTo 3 | -------------------------------------------------------------------------------- /frontend/src/components/count-to/index.less: -------------------------------------------------------------------------------- 1 | @prefix: ~"count-to"; 2 | 3 | .@{prefix}-wrapper{ 4 | .content-outer{ 5 | display: inline-block; 6 | .@{prefix}-unit-text{ 7 | font-style: normal; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/cropper/index.js: -------------------------------------------------------------------------------- 1 | import Cropper from './index.vue' 2 | export default Cropper 3 | -------------------------------------------------------------------------------- /frontend/src/components/cropper/index.less: -------------------------------------------------------------------------------- 1 | .bg{ 2 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC") 3 | } 4 | .cropper-wrapper{ 5 | width: 600px; 6 | height: 340px; 7 | .img-box{ 8 | height: 340px; 9 | width: 430px; 10 | border: 1px solid #ebebeb; 11 | display: inline-block; 12 | .bg; 13 | img{ 14 | max-width: 100%; 15 | display: block; 16 | } 17 | } 18 | .right-con{ 19 | display: inline-block; 20 | width: 170px; 21 | vertical-align: top; 22 | box-sizing: border-box; 23 | padding: 0 10px; 24 | .preview-box{ 25 | height: 150px !important; 26 | width: 100% !important; 27 | overflow: hidden; 28 | border: 1px solid #ebebeb; 29 | .bg; 30 | } 31 | .button-box{ 32 | padding: 10px 0 0; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/components/drag-drawer/drag-drawer-trigger.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 16 | 17 | 19 | -------------------------------------------------------------------------------- /frontend/src/components/drag-drawer/index.js: -------------------------------------------------------------------------------- 1 | import DragDrawer from './drag-drawer.vue' 2 | export default DragDrawer 3 | -------------------------------------------------------------------------------- /frontend/src/components/drag-drawer/index.less: -------------------------------------------------------------------------------- 1 | @prefix: ~"drag-drawer"; 2 | @drag-drawer-trigger-height: 100px; 3 | @drag-drawer-trigger-width: 8px; 4 | 5 | .@{prefix}-wrapper{ 6 | &.no-select{ 7 | user-select: none; 8 | } 9 | &.pointer-events-none{ 10 | pointer-events: none; 11 | & .@{prefix}-trigger-wrapper{ 12 | pointer-events: all; 13 | } 14 | } 15 | .ivu-drawer{ 16 | &-header{ 17 | overflow: hidden !important; 18 | box-sizing: border-box; 19 | } 20 | &-body{ 21 | padding: 0; 22 | overflow: visible; 23 | position: static; 24 | display: flex; 25 | flex-direction: column; 26 | } 27 | } 28 | .@{prefix}-body-wrapper{ 29 | width: 100%; 30 | height: 100%; 31 | padding: 16px; 32 | overflow: auto; 33 | } 34 | .@{prefix}-trigger-wrapper{ 35 | top: 0; 36 | height: 100%; 37 | width: 0; 38 | .@{prefix}-move-trigger{ 39 | position: absolute; 40 | top: 50%; 41 | height: @drag-drawer-trigger-height; 42 | width: @drag-drawer-trigger-width; 43 | background: rgb(243, 243, 243); 44 | transform: translate(-50%, -50%); 45 | border-radius: ~"4px / 6px"; 46 | box-shadow: 0 0 1px 1px rgba(0, 0, 0, .2); 47 | line-height: @drag-drawer-trigger-height; 48 | cursor: col-resize; 49 | &-point{ 50 | display: inline-block; 51 | width: 50%; 52 | transform: translateX(50%); 53 | i{ 54 | display: block; 55 | border-bottom: 1px solid rgb(192, 192, 192); 56 | padding-bottom: 2px; 57 | } 58 | } 59 | } 60 | } 61 | .@{prefix}-footer{ 62 | flex-grow: 1; 63 | width: 100%; 64 | bottom: 0; 65 | left: 0; 66 | border-top: 1px solid #e8e8e8; 67 | padding: 10px 16px; 68 | background: #fff; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /frontend/src/components/drag-drawer/mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | prefix: 'drag-drawer' 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/components/drag-list/drag-list.vue: -------------------------------------------------------------------------------- 1 | 21 | 84 | 93 | -------------------------------------------------------------------------------- /frontend/src/components/drag-list/index.js: -------------------------------------------------------------------------------- 1 | import DragList from './drag-list.vue' 2 | export default DragList 3 | -------------------------------------------------------------------------------- /frontend/src/components/editor/editor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 70 | 71 | 76 | -------------------------------------------------------------------------------- /frontend/src/components/editor/index.js: -------------------------------------------------------------------------------- 1 | import Editor from './editor.vue' 2 | export default Editor 3 | -------------------------------------------------------------------------------- /frontend/src/components/icons/icons.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/icons/index.js: -------------------------------------------------------------------------------- 1 | import Icons from './icons.vue' 2 | export default Icons 3 | -------------------------------------------------------------------------------- /frontend/src/components/info-card/index.js: -------------------------------------------------------------------------------- 1 | import InforCard from './infor-card.vue' 2 | export default InforCard 3 | -------------------------------------------------------------------------------- /frontend/src/components/info-card/infor-card.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 55 | 56 | 95 | -------------------------------------------------------------------------------- /frontend/src/components/login-form/index.js: -------------------------------------------------------------------------------- 1 | import LoginForm from './login-form.vue' 2 | export default LoginForm 3 | -------------------------------------------------------------------------------- /frontend/src/components/login-form/login-form.vue: -------------------------------------------------------------------------------- 1 | 28 | 85 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/a-back-top/index.js: -------------------------------------------------------------------------------- 1 | import ABackTop from './index.vue' 2 | export default ABackTop 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/a-back-top/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 91 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/error-store/error-store.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 38 | 39 | 50 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/error-store/index.js: -------------------------------------------------------------------------------- 1 | import ErrorStore from './error-store.vue' 2 | export default ErrorStore 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/fullscreen/fullscreen.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 75 | 76 | 85 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/fullscreen/index.js: -------------------------------------------------------------------------------- 1 | import Fullscreen from './fullscreen.vue' 2 | export default Fullscreen 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.less: -------------------------------------------------------------------------------- 1 | .custom-bread-crumb{ 2 | display: inline-block; 3 | vertical-align: top; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/header-bar/custom-bread-crumb/custom-bread-crumb.vue: -------------------------------------------------------------------------------- 1 | 11 | 47 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/header-bar/custom-bread-crumb/index.js: -------------------------------------------------------------------------------- 1 | import customBreadCrumb from './custom-bread-crumb.vue' 2 | export default customBreadCrumb 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/header-bar/header-bar.less: -------------------------------------------------------------------------------- 1 | .header-bar{ 2 | width: 100%; 3 | height: 100%; 4 | position: relative; 5 | .custom-content-con{ 6 | float: right; 7 | height: auto; 8 | padding-right: 20px; 9 | line-height: 64px; 10 | & > *{ 11 | float: right; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/header-bar/header-bar.vue: -------------------------------------------------------------------------------- 1 | 10 | 35 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/header-bar/index.js: -------------------------------------------------------------------------------- 1 | import HeaderBar from './header-bar' 2 | export default HeaderBar 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/header-bar/sider-trigger/index.js: -------------------------------------------------------------------------------- 1 | import siderTrigger from './sider-trigger.vue' 2 | export default siderTrigger 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/header-bar/sider-trigger/sider-trigger.less: -------------------------------------------------------------------------------- 1 | .trans{ 2 | transition: transform .2s ease; 3 | } 4 | @size: 40px; 5 | .sider-trigger-a{ 6 | padding: 6px; 7 | width: @size; 8 | height: @size; 9 | display: inline-block; 10 | text-align: center; 11 | color: #5c6b77; 12 | margin-top: 12px; 13 | i{ 14 | .trans; 15 | vertical-align: top; 16 | } 17 | &.collapsed i{ 18 | transform: rotateZ(90deg); 19 | .trans; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/header-bar/sider-trigger/sider-trigger.vue: -------------------------------------------------------------------------------- 1 | 4 | 25 | 28 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/language/index.js: -------------------------------------------------------------------------------- 1 | import Language from './language.vue' 2 | export default Language 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/language/language.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 52 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/side-menu/collapsed-menu.vue: -------------------------------------------------------------------------------- 1 | 12 | 52 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/side-menu/index.js: -------------------------------------------------------------------------------- 1 | import SideMenu from './side-menu.vue' 2 | export default SideMenu 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/side-menu/item-mixin.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | parentItem: { 4 | type: Object, 5 | default: () => {} 6 | }, 7 | theme: String, 8 | iconSize: Number 9 | }, 10 | computed: { 11 | parentName () { 12 | return this.parentItem.name 13 | }, 14 | children () { 15 | return this.parentItem.children 16 | }, 17 | textColor () { 18 | return this.theme === 'dark' ? '#fff' : '#495060' 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/side-menu/mixin.js: -------------------------------------------------------------------------------- 1 | import CommonIcon from '_c/common-icon' 2 | import { showTitle } from '@/libs/util' 3 | export default { 4 | components: { 5 | CommonIcon 6 | }, 7 | methods: { 8 | showTitle (item) { 9 | return showTitle(item, this) 10 | }, 11 | showChildren (item) { 12 | return item.children && (item.children.length > 1 || (item.meta && item.meta.showAlways)) 13 | }, 14 | getNameOrHref (item, children0) { 15 | return item.href ? `isTurnByHref_${item.href}` : (children0 ? item.children[0].name : item.name) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/side-menu/side-menu-item.vue: -------------------------------------------------------------------------------- 1 | 19 | 27 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/side-menu/side-menu.less: -------------------------------------------------------------------------------- 1 | .side-menu-wrapper{ 2 | user-select: none; 3 | .menu-collapsed{ 4 | padding-top: 10px; 5 | 6 | .ivu-dropdown{ 7 | width: 100%; 8 | .ivu-dropdown-rel a{ 9 | width: 100%; 10 | } 11 | } 12 | .ivu-tooltip{ 13 | width: 100%; 14 | .ivu-tooltip-rel{ 15 | width: 100%; 16 | } 17 | .ivu-tooltip-popper .ivu-tooltip-content{ 18 | .ivu-tooltip-arrow{ 19 | border-right-color: #fff; 20 | } 21 | .ivu-tooltip-inner{ 22 | background: #fff; 23 | color: #495060; 24 | } 25 | } 26 | } 27 | 28 | 29 | } 30 | a.drop-menu-a{ 31 | display: inline-block; 32 | padding: 6px 15px; 33 | width: 100%; 34 | text-align: center; 35 | color: #495060; 36 | } 37 | } 38 | .menu-title{ 39 | padding-left: 6px; 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/tags-nav/index.js: -------------------------------------------------------------------------------- 1 | import TagsNav from './tags-nav.vue' 2 | export default TagsNav 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/tags-nav/tags-nav.less: -------------------------------------------------------------------------------- 1 | .no-select{ 2 | -webkit-touch-callout: none; 3 | -webkit-user-select: none; 4 | -khtml-user-select: none; 5 | -moz-user-select: none; 6 | -ms-user-select: none; 7 | user-select: none; 8 | } 9 | .size{ 10 | width: 100%; 11 | height: 100%; 12 | } 13 | .tags-nav{ 14 | position: relative; 15 | border-top: 1px solid #F0F0F0; 16 | border-bottom: 1px solid #F0F0F0; 17 | .no-select; 18 | .size; 19 | .close-con{ 20 | position: absolute; 21 | right: 0; 22 | top: 0; 23 | height: 100%; 24 | width: 32px; 25 | background: #fff; 26 | text-align: center; 27 | z-index: 10; 28 | } 29 | .btn-con{ 30 | position: absolute; 31 | top: 0px; 32 | height: 100%; 33 | background: #fff; 34 | padding-top: 3px; 35 | z-index: 10; 36 | button{ 37 | padding: 6px 4px; 38 | line-height: 14px; 39 | text-align: center; 40 | } 41 | &.left-btn{ 42 | left: 0px; 43 | } 44 | &.right-btn{ 45 | right: 32px; 46 | border-right: 1px solid #F0F0F0; 47 | } 48 | } 49 | .scroll-outer{ 50 | position: absolute; 51 | left: 28px; 52 | right: 61px; 53 | top: 0; 54 | bottom: 0; 55 | box-shadow: 0px 0 3px 2px rgba(100,100,100,.1) inset; 56 | .scroll-body{ 57 | height: ~"calc(100% - 1px)"; 58 | display: inline-block; 59 | padding: 1px 4px 0; 60 | position: absolute; 61 | overflow: visible; 62 | white-space: nowrap; 63 | transition: left .3s ease; 64 | .ivu-tag-dot-inner{ 65 | transition: background .2s ease; 66 | } 67 | } 68 | } 69 | .contextmenu { 70 | position: absolute; 71 | margin: 0; 72 | padding: 5px 0; 73 | background: #fff; 74 | z-index: 1000; 75 | list-style-type: none; 76 | border-radius: 4px; 77 | box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .1); 78 | li { 79 | margin: 0; 80 | padding: 5px 15px; 81 | cursor: pointer; 82 | &:hover { 83 | background: #eee; 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/user/index.js: -------------------------------------------------------------------------------- 1 | import User from './user.vue' 2 | export default User 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/user/user.less: -------------------------------------------------------------------------------- 1 | .user{ 2 | &-avatar-dropdown{ 3 | cursor: pointer; 4 | display: inline-block; 5 | // height: 64px; 6 | vertical-align: middle; 7 | // line-height: 64px; 8 | .ivu-badge-dot{ 9 | top: 16px; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/components/main/components/user/user.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 62 | -------------------------------------------------------------------------------- /frontend/src/components/main/index.js: -------------------------------------------------------------------------------- 1 | import Main from './main.vue' 2 | export default Main 3 | -------------------------------------------------------------------------------- /frontend/src/components/main/main.less: -------------------------------------------------------------------------------- 1 | .main{ 2 | .logo-con{ 3 | height: 64px; 4 | padding: 10px; 5 | img{ 6 | height: 44px; 7 | width: auto; 8 | display: block; 9 | margin: 0 auto; 10 | } 11 | } 12 | .header-con{ 13 | background: #fff; 14 | padding: 0 20px; 15 | width: 100%; 16 | } 17 | .main-layout-con{ 18 | height: 100%; 19 | overflow: hidden; 20 | } 21 | .main-content-con{ 22 | height: ~"calc(100% - 60px)"; 23 | overflow: hidden; 24 | } 25 | .tag-nav-wrapper{ 26 | padding: 0; 27 | height:40px; 28 | background:#F0F0F0; 29 | } 30 | .content-wrapper{ 31 | padding: 18px; 32 | height: ~"calc(100% - 80px)"; 33 | overflow: auto; 34 | } 35 | .left-sider{ 36 | .ivu-layout-sider-children{ 37 | overflow-y: scroll; 38 | margin-right: -18px; 39 | } 40 | } 41 | } 42 | .ivu-menu-item > i{ 43 | margin-right: 12px !important; 44 | } 45 | .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { 46 | margin-right: 8px !important; 47 | } 48 | .collased-menu-dropdown{ 49 | width: 100%; 50 | margin: 0; 51 | line-height: normal; 52 | padding: 7px 0 6px 16px; 53 | clear: both; 54 | font-size: 12px !important; 55 | white-space: nowrap; 56 | list-style: none; 57 | cursor: pointer; 58 | transition: background 0.2s ease-in-out; 59 | &:hover{ 60 | background: rgba(100, 100, 100, 0.1); 61 | } 62 | & * { 63 | color: #515a6e; 64 | } 65 | .ivu-menu-item > i{ 66 | margin-right: 12px !important; 67 | } 68 | .ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i { 69 | margin-right: 8px !important; 70 | } 71 | } 72 | 73 | .ivu-select-dropdown.ivu-dropdown-transfer{ 74 | max-height: 400px; 75 | } 76 | -------------------------------------------------------------------------------- /frontend/src/components/markdown/index.js: -------------------------------------------------------------------------------- 1 | import MarkdownEditor from './markdown.vue' 2 | export default MarkdownEditor 3 | -------------------------------------------------------------------------------- /frontend/src/components/markdown/markdown.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 63 | 64 | 77 | -------------------------------------------------------------------------------- /frontend/src/components/parent-view/index.js: -------------------------------------------------------------------------------- 1 | import ParentView from './parent-view.vue' 2 | export default ParentView 3 | -------------------------------------------------------------------------------- /frontend/src/components/parent-view/parent-view.vue: -------------------------------------------------------------------------------- 1 | 6 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/paste-editor/index.js: -------------------------------------------------------------------------------- 1 | import PasteEditor from './paste-editor.vue' 2 | export default PasteEditor 3 | -------------------------------------------------------------------------------- /frontend/src/components/paste-editor/paste-editor.less: -------------------------------------------------------------------------------- 1 | .paste-editor-wrapper{ 2 | width: 100%; 3 | height: 100%; 4 | border: 1px dashed gainsboro; 5 | textarea.textarea-el{ 6 | width: 100%; 7 | height: 100%; 8 | } 9 | .CodeMirror{ 10 | height: 100%; 11 | padding: 0; 12 | .CodeMirror-code div .CodeMirror-line > span > span.cm-tab{ 13 | &::after{ 14 | content: '→'; 15 | color: #BFBFBF; 16 | } 17 | } 18 | } 19 | .first-row{ 20 | font-weight: 700; 21 | font-size: 14px; 22 | } 23 | .incorrect-row{ 24 | background: #F5CBD1; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/components/paste-editor/plugins/placeholder.js: -------------------------------------------------------------------------------- 1 | export default (codemirror) => { 2 | (function (mod) { 3 | mod(codemirror) 4 | })(function (CodeMirror) { 5 | CodeMirror.defineOption('placeholder', '', function (cm, val, old) { 6 | var prev = old && old !== CodeMirror.Init 7 | if (val && !prev) { 8 | cm.on('blur', onBlur) 9 | cm.on('change', onChange) 10 | cm.on('swapDoc', onChange) 11 | onChange(cm) 12 | } else if (!val && prev) { 13 | cm.off('blur', onBlur) 14 | cm.off('change', onChange) 15 | cm.off('swapDoc', onChange) 16 | clearPlaceholder(cm) 17 | var wrapper = cm.getWrapperElement() 18 | wrapper.className = wrapper.className.replace(' CodeMirror-empty', '') 19 | } 20 | 21 | if (val && !cm.hasFocus()) onBlur(cm) 22 | }) 23 | 24 | function clearPlaceholder (cm) { 25 | if (cm.state.placeholder) { 26 | cm.state.placeholder.parentNode.removeChild(cm.state.placeholder) 27 | cm.state.placeholder = null 28 | } 29 | } 30 | function setPlaceholder (cm) { 31 | clearPlaceholder(cm) 32 | var elt = cm.state.placeholder = document.createElement('pre') 33 | elt.style.cssText = 'height: 0; overflow: visible; color: #80848f;' 34 | elt.style.direction = cm.getOption('direction') 35 | elt.className = 'CodeMirror-placeholder' 36 | var placeHolder = cm.getOption('placeholder') 37 | if (typeof placeHolder === 'string') placeHolder = document.createTextNode(placeHolder) 38 | elt.appendChild(placeHolder) 39 | cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild) 40 | } 41 | 42 | function onBlur (cm) { 43 | if (isEmpty(cm)) setPlaceholder(cm) 44 | } 45 | function onChange (cm) { 46 | let wrapper = cm.getWrapperElement() 47 | let empty = isEmpty(cm) 48 | wrapper.className = wrapper.className.replace(' CodeMirror-empty', '') + (empty ? ' CodeMirror-empty' : '') 49 | 50 | if (empty) setPlaceholder(cm) 51 | else clearPlaceholder(cm) 52 | } 53 | 54 | function isEmpty (cm) { 55 | return (cm.lineCount() === 1) && (cm.getLine(0) === '') 56 | } 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /frontend/src/components/split-pane/index.js: -------------------------------------------------------------------------------- 1 | import Split from './split.vue' 2 | export default Split 3 | -------------------------------------------------------------------------------- /frontend/src/components/split-pane/trigger.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 40 | 41 | 44 | -------------------------------------------------------------------------------- /frontend/src/components/tables/edit.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 45 | 46 | 74 | -------------------------------------------------------------------------------- /frontend/src/components/tables/handle-btns.js: -------------------------------------------------------------------------------- 1 | const btns = { 2 | delete: (h, params, vm) => { 3 | return h('Poptip', { 4 | props: { 5 | confirm: true, 6 | title: '你确定要删除吗?' 7 | }, 8 | on: { 9 | 'on-ok': () => { 10 | vm.$emit('on-delete', params) 11 | vm.$emit('input', params.tableData.filter((item, index) => index !== params.row.initRowIndex)) 12 | } 13 | } 14 | }, [ 15 | h('Button', { 16 | props: { 17 | type: 'text', 18 | ghost: true 19 | } 20 | }, [ 21 | h('Icon', { 22 | props: { 23 | type: 'md-trash', 24 | size: 18, 25 | color: '#000000' 26 | } 27 | }) 28 | ]) 29 | ]) 30 | } 31 | } 32 | 33 | export default btns 34 | -------------------------------------------------------------------------------- /frontend/src/components/tables/index.js: -------------------------------------------------------------------------------- 1 | import Tables from './tables.vue' 2 | export default Tables 3 | -------------------------------------------------------------------------------- /frontend/src/components/tables/index.less: -------------------------------------------------------------------------------- 1 | .search-con{ 2 | padding: 10px 0; 3 | .search{ 4 | &-col{ 5 | display: inline-block; 6 | width: 200px; 7 | } 8 | &-input{ 9 | display: inline-block; 10 | width: 200px; 11 | margin-left: 2px; 12 | } 13 | &-btn{ 14 | margin-left: 2px; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/components/tree-select/index.js: -------------------------------------------------------------------------------- 1 | export { default } from './tree-select.vue' 2 | -------------------------------------------------------------------------------- /frontend/src/components/tree-select/tree-select.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /frontend/src/config/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | /** 3 | * @description 配置显示在浏览器标签的title 4 | */ 5 | title: 'iView-admin', 6 | /** 7 | * @description token在Cookie中存储的天数,默认1天 8 | */ 9 | cookieExpires: 1, 10 | /** 11 | * @description 是否使用国际化,默认为false 12 | * 如果不使用,则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'} 13 | * 用来在菜单中显示文字 14 | */ 15 | useI18n: true, 16 | /** 17 | * @description api请求基础路径 18 | */ 19 | baseUrl: { 20 | dev: 'http://172.16.43.216:8000', 21 | pro: 'https://produce.com' 22 | }, 23 | /** 24 | * @description 默认打开的首页的路由name值,默认为home 25 | */ 26 | homeName: 'home', 27 | /** 28 | * @description 需要加载的插件 29 | */ 30 | plugin: { 31 | 'error-store': { 32 | showInHeader: true, // 设为false后不会在顶部显示错误日志徽标 33 | developmentOff: true // 设为true后在开发环境不会收集错误信息,方便开发中排查错误 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /frontend/src/directive/directives.js: -------------------------------------------------------------------------------- 1 | import draggable from './module/draggable' 2 | import clipboard from './module/clipboard' 3 | 4 | const directives = { 5 | draggable, 6 | clipboard 7 | } 8 | 9 | export default directives 10 | -------------------------------------------------------------------------------- /frontend/src/directive/index.js: -------------------------------------------------------------------------------- 1 | import directive from './directives' 2 | 3 | const importDirective = Vue => { 4 | /** 5 | * 拖拽指令 v-draggable="options" 6 | * options = { 7 | * trigger: /这里传入作为拖拽触发器的CSS选择器/, 8 | * body: /这里传入需要移动容器的CSS选择器/, 9 | * recover: /拖动结束之后是否恢复到原来的位置/ 10 | * } 11 | */ 12 | Vue.directive('draggable', directive.draggable) 13 | /** 14 | * clipboard指令 v-draggable="options" 15 | * options = { 16 | * value: /在输入框中使用v-model绑定的值/, 17 | * success: /复制成功后的回调/, 18 | * error: /复制失败后的回调/ 19 | * } 20 | */ 21 | Vue.directive('clipboard', directive.clipboard) 22 | } 23 | 24 | export default importDirective 25 | -------------------------------------------------------------------------------- /frontend/src/directive/module/clipboard.js: -------------------------------------------------------------------------------- 1 | import Clipboard from 'clipboard' 2 | export default { 3 | bind: (el, binding) => { 4 | const clipboard = new Clipboard(el, { 5 | text: () => binding.value.value 6 | }) 7 | el.__success_callback__ = binding.value.success 8 | el.__error_callback__ = binding.value.error 9 | clipboard.on('success', e => { 10 | const callback = el.__success_callback__ 11 | callback && callback(e) 12 | }) 13 | clipboard.on('error', e => { 14 | const callback = el.__error_callback__ 15 | callback && callback(e) 16 | }) 17 | el.__clipboard__ = clipboard 18 | }, 19 | update: (el, binding) => { 20 | el.__clipboard__.text = () => binding.value.value 21 | el.__success_callback__ = binding.value.success 22 | el.__error_callback__ = binding.value.error 23 | }, 24 | unbind: (el, binding) => { 25 | delete el.__success_callback__ 26 | delete el.__error_callback__ 27 | el.__clipboard__.destroy() 28 | delete el.__clipboard__ 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/directive/module/draggable.js: -------------------------------------------------------------------------------- 1 | import { on } from '@/libs/tools' 2 | export default { 3 | inserted: (el, binding, vnode) => { 4 | let triggerDom = document.querySelector(binding.value.trigger) 5 | triggerDom.style.cursor = 'move' 6 | let bodyDom = document.querySelector(binding.value.body) 7 | let pageX = 0 8 | let pageY = 0 9 | let transformX = 0 10 | let transformY = 0 11 | let canMove = false 12 | const handleMousedown = e => { 13 | let transform = /\(.*\)/.exec(bodyDom.style.transform) 14 | if (transform) { 15 | transform = transform[0].slice(1, transform[0].length - 1) 16 | let splitxy = transform.split('px, ') 17 | transformX = parseFloat(splitxy[0]) 18 | transformY = parseFloat(splitxy[1].split('px')[0]) 19 | } 20 | pageX = e.pageX 21 | pageY = e.pageY 22 | canMove = true 23 | } 24 | const handleMousemove = e => { 25 | let xOffset = e.pageX - pageX + transformX 26 | let yOffset = e.pageY - pageY + transformY 27 | if (canMove) bodyDom.style.transform = `translate(${xOffset}px, ${yOffset}px)` 28 | } 29 | const handleMouseup = e => { 30 | canMove = false 31 | } 32 | on(triggerDom, 'mousedown', handleMousedown) 33 | on(document, 'mousemove', handleMousemove) 34 | on(document, 'mouseup', handleMouseup) 35 | }, 36 | update: (el, binding, vnode) => { 37 | if (!binding.value.recover) return 38 | let bodyDom = document.querySelector(binding.value.body) 39 | bodyDom.style.transform = '' 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /frontend/src/index.less: -------------------------------------------------------------------------------- 1 | @import '~iview/src/styles/index.less'; 2 | 3 | @menu-dark-title: #001529; 4 | @menu-dark-active-bg: #000c17; 5 | @layout-sider-background: #001529; 6 | -------------------------------------------------------------------------------- /frontend/src/libs/api.request.js: -------------------------------------------------------------------------------- 1 | import HttpRequest from '@/libs/axios' 2 | import config from '@/config' 3 | const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro 4 | 5 | const axios = new HttpRequest(baseUrl) 6 | export default axios 7 | -------------------------------------------------------------------------------- /frontend/src/libs/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import Cookies from 'js-cookie' 3 | import iView from 'iview' 4 | import router from '@/router' 5 | 6 | function permerror (nodesc, title, desc) { 7 | iView.Notice.error({ 8 | duration: 10, 9 | title: title, 10 | desc: nodesc ? '' : desc 11 | }) 12 | } 13 | 14 | class HttpRequest { 15 | constructor (baseUrl = baseURL) { 16 | this.baseUrl = baseUrl 17 | this.queue = {} 18 | } 19 | 20 | getInsideConfig () { 21 | const config = { 22 | baseURL: this.baseUrl, 23 | headers: {} 24 | } 25 | return config 26 | } 27 | 28 | destroy (url) { 29 | delete this.queue[url] 30 | if (!Object.keys(this.queue).length) { 31 | // Spin.hide() 32 | } 33 | } 34 | 35 | interceptors (instance, url) { 36 | // 请求拦截 37 | instance.interceptors.request.use(config => { 38 | // 添加全局的loading... 39 | if (!Object.keys(this.queue).length) { 40 | // Spin.show() // 不建议开启,因为界面不友好 41 | } 42 | let token = Cookies.get('token') 43 | if (token) { // 获取到了本地的token 44 | config.headers.Authorization = 'JWT ' + token 45 | } 46 | this.queue[url] = true 47 | return config 48 | }, error => { 49 | return Promise.reject(error) 50 | }) 51 | // 响应拦截 52 | instance.interceptors.response.use(res => { 53 | this.destroy(url) 54 | const { data, status } = res 55 | return { data, status } 56 | }, error => { 57 | if (error.response) { 58 | switch (error.response.status) { 59 | case 400: 60 | permerror(false, error.response.request.statusText, error.response.request.responseText) 61 | break 62 | case 401: // 拦截验证token失败的请求,清除token信息并跳转到登录页面 63 | router.push({ 64 | name: 'login' 65 | }) 66 | break 67 | case 403: 68 | permerror(false, error.response.statusText, error.response.data.detail) 69 | break 70 | case 500: 71 | permerror(false, error.response.status, error.response.statusText) 72 | break 73 | } 74 | } 75 | return Promise.reject(error) 76 | }) 77 | } 78 | 79 | request (options) { 80 | const instance = axios.create() 81 | options = Object.assign(this.getInsideConfig(), options) 82 | this.interceptors(instance, options.url) 83 | return instance(options) 84 | } 85 | } 86 | export default HttpRequest 87 | -------------------------------------------------------------------------------- /frontend/src/libs/render-dom.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'RenderDom', 3 | functional: true, 4 | props: { 5 | render: Function 6 | }, 7 | render: (h, ctx) => { 8 | return ctx.props.render(h) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/libs/view/common.js: -------------------------------------------------------------------------------- 1 | 2 | export const alertWarning = (action, notice, name_id) => { 3 | let action_map = { 4 | 'create': '创建', 5 | 'update': '更新', 6 | 'delete': '删除', 7 | 'fetchInfo': '采集信息', 8 | 'sync': '同步信息' 9 | } 10 | let action_desc = action_map[action] 11 | notice.success({ 12 | title: action_desc, 13 | duration: 6, 14 | render: h => { 15 | if (action === 'create') { 16 | var desc = h('p', {}, name_id + ' 已' + action_desc) 17 | } else { 18 | desc = h('p', {}, '资源(ID: ' + name_id + ' )已' + action_desc) 19 | } 20 | let subTags = [desc] 21 | return h('div', subTags) 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/locale/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import { localRead } from '@/libs/util' 4 | import customZhCn from './lang/zh-CN' 5 | import customZhTw from './lang/zh-TW' 6 | import customEnUs from './lang/en-US' 7 | import zhCnLocale from 'iview/src/locale/lang/zh-CN' 8 | import enUsLocale from 'iview/src/locale/lang/en-US' 9 | import zhTwLocale from 'iview/src/locale/lang/zh-TW' 10 | 11 | Vue.use(VueI18n) 12 | 13 | // 自动根据浏览器系统语言设置语言 14 | const navLang = navigator.language 15 | const localLang = (navLang === 'zh-CN' || navLang === 'en-US') ? navLang : false 16 | let lang = localLang || localRead('local') || 'zh-CN' 17 | 18 | Vue.config.lang = lang 19 | 20 | // vue-i18n 6.x+写法 21 | Vue.locale = () => {} 22 | const messages = { 23 | 'zh-CN': Object.assign(zhCnLocale, customZhCn), 24 | 'zh-TW': Object.assign(zhTwLocale, customZhTw), 25 | 'en-US': Object.assign(enUsLocale, customEnUs) 26 | } 27 | const i18n = new VueI18n({ 28 | locale: lang, 29 | messages, 30 | silentTranslationWarn: true 31 | }) 32 | 33 | export default i18n 34 | 35 | // vue-i18n 5.x写法 36 | // Vue.locale('zh-CN', Object.assign(zhCnLocale, customZhCn)) 37 | // Vue.locale('en-US', Object.assign(zhTwLocale, customZhTw)) 38 | // Vue.locale('zh-TW', Object.assign(enUsLocale, customEnUs)) 39 | -------------------------------------------------------------------------------- /frontend/src/locale/lang/en-US.js: -------------------------------------------------------------------------------- 1 | export default { 2 | home: 'Home', 3 | login: 'Login', 4 | components: 'Components', 5 | count_to_page: 'Count-to', 6 | tables_page: 'Table', 7 | split_pane_page: 'Split-pane', 8 | markdown_page: 'Markdown-editor', 9 | editor_page: 'Rich-Text-Editor', 10 | icons_page: 'Custom-icon', 11 | img_cropper_page: 'Image-editor', 12 | update: 'Update', 13 | doc: 'Document', 14 | join_page: 'QQ Group', 15 | update_table_page: 'Update .CSV', 16 | update_paste_page: 'Paste Table Data', 17 | multilevel: 'multilevel', 18 | directive_page: 'Directive', 19 | level_1: 'Level-1', 20 | level_2: 'Level-2', 21 | level_2_1: 'Level-2-1', 22 | level_2_3: 'Level-2-3', 23 | level_2_2: 'Level-2-2', 24 | level_2_2_1: 'Level-2-2-1', 25 | level_2_2_2: 'Level-2-2-2', 26 | excel: 'Excel', 27 | 'upload-excel': 'Upload Excel', 28 | 'export-excel': 'Export Excel', 29 | tools_methods_page: 'Tools Methods', 30 | drag_list_page: 'Drag-list', 31 | i18n_page: 'Internationalization', 32 | modalTitle: 'Modal Title', 33 | content: 'This is the modal box content.', 34 | buttonText: 'Show Modal', 35 | 'i18n-tip': 'Note: Only this page is multi-language, other pages do not add language content to the multi-language package.', 36 | error_store_page: 'Error Collection', 37 | error_logger_page: 'Error Logger', 38 | query: 'Query', 39 | params: 'Params', 40 | cropper_page: 'Cropper', 41 | message_page: 'Message Center', 42 | tree_table_page: 'Tree Table', 43 | org_tree_page: 'Org Tree', 44 | drag_drawer_page: 'Draggable Drawer', 45 | tree_select_page: 'Tree Selector' 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/locale/lang/zh-CN.js: -------------------------------------------------------------------------------- 1 | export default { 2 | home: 'Dashboard', 3 | login: '登录', 4 | components: '组件', 5 | count_to_page: '数字渐变', 6 | tables_page: '多功能表格', 7 | split_pane_page: '分割窗口', 8 | markdown_page: 'Markdown编辑器', 9 | editor_page: '富文本编辑器', 10 | icons_page: '自定义图标', 11 | img_cropper_page: '图片编辑器', 12 | update: '上传数据', 13 | join_page: 'QQ群', 14 | doc: '文档', 15 | update_table_page: '上传CSV文件', 16 | update_paste_page: '粘贴表格数据', 17 | multilevel: '多级菜单', 18 | directive_page: '指令', 19 | level_1: 'Level-1', 20 | level_2: 'Level-2', 21 | level_2_1: 'Level-2-1', 22 | level_2_3: 'Level-2-3', 23 | level_2_2: 'Level-2-2', 24 | level_2_2_1: 'Level-2-2-1', 25 | level_2_2_2: 'Level-2-2-2', 26 | excel: 'Excel', 27 | 'upload-excel': '上传excel', 28 | 'export-excel': '导出excel', 29 | tools_methods_page: '工具函数', 30 | drag_list_page: '拖拽列表', 31 | i18n_page: '多语言', 32 | modalTitle: '模态框题目', 33 | content: '这是模态框内容', 34 | buttonText: '显示模态框', 35 | 'i18n-tip': '注:仅此页做了多语言,其他页面没有在多语言包中添加语言内容', 36 | error_store_page: '错误收集', 37 | error_logger_page: '错误日志', 38 | query: '带参路由', 39 | params: '动态路由', 40 | cropper_page: '图片裁剪', 41 | message_page: '消息中心', 42 | tree_table_page: '树状表格', 43 | org_tree_page: '组织结构树', 44 | drag_drawer_page: '可拖动抽屉', 45 | tree_select_page: '树状下拉选择器', 46 | cmdb: 'CMDB', 47 | data_center: '数据中心', 48 | idcs: '机房', 49 | idc_detail: '机房详情', 50 | racks: '机柜', 51 | rack_detail: '机柜详情', 52 | servers: '服务器', 53 | server_detail: '服务器详情', 54 | sshusers: 'SSH用户', 55 | business_info: '业务信息', 56 | businesslines: '业务线', 57 | projects: '项目', 58 | account: '用户和组', 59 | users: '用户管理', 60 | groups: '组管理', 61 | histories: '历史记录', 62 | history_detail: '历史详情' 63 | } 64 | -------------------------------------------------------------------------------- /frontend/src/locale/lang/zh-TW.js: -------------------------------------------------------------------------------- 1 | export default { 2 | home: '首頁', 3 | login: '登錄', 4 | components: '组件', 5 | count_to_page: '数字渐变', 6 | tables_page: '多功能表格', 7 | split_pane_page: '分割窗口', 8 | markdown_page: 'Markdown編輯器', 9 | editor_page: '富文本編輯器', 10 | icons_page: '自定義圖標', 11 | img_cropper_page: '圖片編輯器', 12 | update: '上傳數據', 13 | join_page: 'QQ群', 14 | doc: '文檔', 15 | update_table_page: '上傳CSV文件', 16 | update_paste_page: '粘貼表格數據', 17 | multilevel: '多级菜单', 18 | directive_page: '指令', 19 | level_1: 'Level-1', 20 | level_2: 'Level-2', 21 | level_2_1: 'Level-2-1', 22 | level_2_3: 'Level-2-3', 23 | level_2_2: 'Level-2-2', 24 | level_2_2_1: 'Level-2-2-1', 25 | level_2_2_2: 'Level-2-2-2', 26 | excel: 'Excel', 27 | 'upload-excel': '上傳excel', 28 | 'export-excel': '導出excel', 29 | tools_methods_page: '工具函數', 30 | drag_list_page: '拖拽列表', 31 | i18n_page: '多語言', 32 | modalTitle: '模態框題目', 33 | content: '這是模態框內容', 34 | buttonText: '顯示模態框', 35 | 'i18n-tip': '注:僅此頁做了多語言,其他頁面沒有在多語言包中添加語言內容', 36 | error_store_page: '錯誤收集', 37 | error_logger_page: '錯誤日誌', 38 | query: '帶參路由', 39 | params: '動態路由', 40 | cropper_page: '圖片裁剪', 41 | message_page: '消息中心', 42 | tree_table_page: '樹狀表格', 43 | org_tree_page: '組織結構樹', 44 | drag_drawer_page: '可拖動抽屜', 45 | tree_select_page: '樹狀下拉選擇器' 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import store from './store' 7 | import iView from 'iview' 8 | import i18n from '@/locale' 9 | import config from '@/config' 10 | import importDirective from '@/directive' 11 | import { directive as clickOutside } from 'v-click-outside-x' 12 | import installPlugin from '@/plugin' 13 | import './index.less' 14 | import '@/assets/icons/iconfont.css' 15 | import TreeTable from 'tree-table-vue' 16 | import VOrgTree from 'v-org-tree' 17 | import 'v-org-tree/dist/v-org-tree.css' 18 | import 'xterm/dist/xterm.css' 19 | 20 | // 实际打包时应该不引入mock 21 | /* eslint-disable */ 22 | if (process.env.NODE_ENV !== 'production') require('@/mock') 23 | 24 | Vue.use(iView, { 25 | i18n: (key, value) => i18n.t(key, value) 26 | }) 27 | Vue.use(TreeTable) 28 | Vue.use(VOrgTree) 29 | /** 30 | * @description 注册admin内置插件 31 | */ 32 | installPlugin(Vue) 33 | /** 34 | * @description 生产环境关掉提示 35 | */ 36 | Vue.config.productionTip = false 37 | /** 38 | * @description 全局注册应用配置 39 | */ 40 | Vue.prototype.$config = config 41 | /** 42 | * 注册指令 43 | */ 44 | importDirective(Vue) 45 | Vue.directive('clickOutside', clickOutside) 46 | 47 | /* eslint-disable no-new */ 48 | new Vue({ 49 | el: '#app', 50 | router, 51 | i18n, 52 | store, 53 | render: h => h(App) 54 | }) 55 | -------------------------------------------------------------------------------- /frontend/src/mock/data.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { doCustomTimes } from '@/libs/util' 3 | import orgData from './data/org-data' 4 | import { treeData } from './data/tree-select' 5 | const Random = Mock.Random 6 | 7 | export const getTableData = req => { 8 | let tableData = [] 9 | doCustomTimes(5, () => { 10 | tableData.push(Mock.mock({ 11 | name: '@name', 12 | email: '@email', 13 | createTime: '@date' 14 | })) 15 | }) 16 | return tableData 17 | } 18 | 19 | export const getDragList = req => { 20 | let dragList = [] 21 | doCustomTimes(5, () => { 22 | dragList.push(Mock.mock({ 23 | name: Random.csentence(10, 13), 24 | id: Random.increment(10) 25 | })) 26 | }) 27 | return dragList 28 | } 29 | 30 | export const uploadImage = req => { 31 | return Promise.resolve() 32 | } 33 | 34 | export const getOrgData = req => { 35 | return orgData 36 | } 37 | 38 | export const getTreeSelectData = req => { 39 | return treeData 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/mock/data/org-data.js: -------------------------------------------------------------------------------- 1 | export default { 2 | id: 0, 3 | label: 'XXX科技有限公司', 4 | children: [ 5 | { 6 | id: 2, 7 | label: '产品研发部', 8 | children: [ 9 | { 10 | id: 5, 11 | label: '研发-前端' 12 | }, { 13 | id: 6, 14 | label: '研发-后端' 15 | }, { 16 | id: 9, 17 | label: 'UI设计' 18 | }, { 19 | id: 10, 20 | label: '产品经理' 21 | } 22 | ] 23 | }, 24 | { 25 | id: 3, 26 | label: '销售部', 27 | children: [ 28 | { 29 | id: 7, 30 | label: '销售一部' 31 | }, { 32 | id: 8, 33 | label: '销售二部' 34 | } 35 | ] 36 | }, 37 | { 38 | id: 4, 39 | label: '财务部' 40 | }, { 41 | id: 11, 42 | label: 'HR人事' 43 | } 44 | ] 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/mock/data/tree-select.js: -------------------------------------------------------------------------------- 1 | export const treeData = [ 2 | { 3 | id: 1, 4 | title: '1', 5 | children: [ 6 | { 7 | id: 11, 8 | title: '1-1', 9 | loading: false, 10 | children: [ 11 | // { 12 | // id: 111, 13 | // title: '1-1-1' 14 | // }, 15 | // { 16 | // id: 112, 17 | // title: '1-1-2' 18 | // }, 19 | // { 20 | // id: 113, 21 | // title: '1-1-3' 22 | // }, 23 | // { 24 | // id: 114, 25 | // title: '1-1-4' 26 | // } 27 | ] 28 | }, 29 | { 30 | id: 12, 31 | title: '1-2', 32 | children: [ 33 | { 34 | id: 121, 35 | title: '1-2-1' 36 | } 37 | ] 38 | } 39 | ] 40 | } 41 | ] 42 | 43 | export const newTreeData = [ 44 | { 45 | id: 'a', 46 | title: 'a', 47 | children: [ 48 | { 49 | id: 'a1', 50 | title: 'a-1', 51 | children: [ 52 | { 53 | id: 112, 54 | title: '1-1-2' 55 | }, 56 | { 57 | id: 'a12', 58 | title: 'a-1-2' 59 | }, 60 | { 61 | id: 'a13', 62 | title: 'a-1-3' 63 | }, 64 | { 65 | id: 'a14', 66 | title: 'a-1-4' 67 | } 68 | ] 69 | }, 70 | { 71 | id: 'a2', 72 | title: 'a-2', 73 | children: [ 74 | { 75 | id: 'a21', 76 | title: 'b-2-1' 77 | } 78 | ] 79 | } 80 | ] 81 | } 82 | ] 83 | -------------------------------------------------------------------------------- /frontend/src/mock/index.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { login, logout, getUserInfo } from './login' 3 | import { getTableData, getDragList, uploadImage, getOrgData, getTreeSelectData } from './data' 4 | import { getMessageInit, getContentByMsgId, hasRead, removeReaded, restoreTrash, messageCount } from './user' 5 | 6 | // 配置Ajax请求延时,可用来测试网络延迟大时项目中一些效果 7 | Mock.setup({ 8 | timeout: 1000 9 | }) 10 | 11 | // 登录相关和获取用户信息 12 | Mock.mock(/\/login/, login) 13 | Mock.mock(/\/get_info/, getUserInfo) 14 | Mock.mock(/\/logout/, logout) 15 | Mock.mock(/\/get_table_data/, getTableData) 16 | Mock.mock(/\/get_drag_list/, getDragList) 17 | Mock.mock(/\/save_error_logger/, 'success') 18 | Mock.mock(/\/image\/upload/, uploadImage) 19 | Mock.mock(/\/message\/init/, getMessageInit) 20 | Mock.mock(/\/message\/content/, getContentByMsgId) 21 | Mock.mock(/\/message\/has_read/, hasRead) 22 | Mock.mock(/\/message\/remove_readed/, removeReaded) 23 | Mock.mock(/\/message\/restore/, restoreTrash) 24 | Mock.mock(/\/message\/count/, messageCount) 25 | Mock.mock(/\/get_org_data/, getOrgData) 26 | Mock.mock(/\/get_tree_select_data/, getTreeSelectData) 27 | 28 | export default Mock 29 | -------------------------------------------------------------------------------- /frontend/src/mock/login.js: -------------------------------------------------------------------------------- 1 | import { getParams } from '@/libs/util' 2 | const USER_MAP = { 3 | super_admin: { 4 | name: 'super_admin', 5 | user_id: '1', 6 | access: ['super_admin', 'admin'], 7 | token: 'super_admin', 8 | avatar: 'https://file.iviewui.com/dist/a0e88e83800f138b94d2414621bd9704.png' 9 | }, 10 | admin: { 11 | name: 'admin', 12 | user_id: '2', 13 | access: ['admin'], 14 | token: 'admin', 15 | avatar: 'https://avatars0.githubusercontent.com/u/20942571?s=460&v=4' 16 | } 17 | } 18 | 19 | export const login = req => { 20 | req = JSON.parse(req.body) 21 | return { token: USER_MAP[req.userName].token } 22 | } 23 | 24 | export const getUserInfo = req => { 25 | const params = getParams(req.url) 26 | return USER_MAP[params.token] 27 | } 28 | 29 | export const logout = req => { 30 | return null 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/mock/user.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs' 2 | import { doCustomTimes } from '@/libs/util' 3 | const Random = Mock.Random 4 | 5 | export const getMessageInit = () => { 6 | let unreadList = [] 7 | doCustomTimes(3, () => { 8 | unreadList.push(Mock.mock({ 9 | title: Random.cword(10, 15), 10 | create_time: '@date', 11 | msg_id: Random.increment(100) 12 | })) 13 | }) 14 | let readedList = [] 15 | doCustomTimes(4, () => { 16 | readedList.push(Mock.mock({ 17 | title: Random.cword(10, 15), 18 | create_time: '@date', 19 | msg_id: Random.increment(100) 20 | })) 21 | }) 22 | let trashList = [] 23 | doCustomTimes(2, () => { 24 | trashList.push(Mock.mock({ 25 | title: Random.cword(10, 15), 26 | create_time: '@date', 27 | msg_id: Random.increment(100) 28 | })) 29 | }) 30 | return { 31 | unread: unreadList, 32 | readed: readedList, 33 | trash: trashList 34 | } 35 | } 36 | 37 | export const getContentByMsgId = () => { 38 | return `
        这是消息内容,这个内容是使用富文本编辑器编辑的,所以你可以看到一些格式
  1. 你可以查看Mock返回的数据格式,和api请求的接口,来确定你的后端接口的开发
  2. 使用你的真实接口后,前端页面基本不需要修改即可满足基本需求
  3. 快来试试吧

${Random.csentence(100, 200)}

` 39 | } 40 | 41 | export const hasRead = () => { 42 | return true 43 | } 44 | 45 | export const removeReaded = () => { 46 | return true 47 | } 48 | 49 | export const restoreTrash = () => { 50 | return true 51 | } 52 | 53 | export const messageCount = () => { 54 | return 3 55 | } 56 | -------------------------------------------------------------------------------- /frontend/src/plugin/error-store/index.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | export default { 3 | install (Vue, options) { 4 | if (options.developmentOff && process.env.NODE_ENV === 'development') return 5 | Vue.config.errorHandler = (error, vm, mes) => { 6 | let info = { 7 | type: 'script', 8 | code: 0, 9 | mes: error.message, 10 | url: window.location.href 11 | } 12 | Vue.nextTick(() => { 13 | store.dispatch('addErrorLog', info) 14 | }) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/plugin/index.js: -------------------------------------------------------------------------------- 1 | import config from '@/config' 2 | const { plugin } = config 3 | 4 | export default (Vue) => { 5 | for (let name in plugin) { 6 | const value = plugin[name] 7 | Vue.use(require(`./${name}`).default, typeof value === 'object' ? value : undefined) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/router/before-close.js: -------------------------------------------------------------------------------- 1 | import { Modal } from 'iview' 2 | 3 | const beforeClose = { 4 | before_close_normal: (resolve) => { 5 | Modal.confirm({ 6 | title: '确定要关闭这一页吗', 7 | onOk: () => { 8 | resolve(true) 9 | }, 10 | onCancel: () => { 11 | resolve(false) 12 | } 13 | }) 14 | } 15 | } 16 | 17 | export default beforeClose 18 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Cookies from 'js-cookie' 3 | import Router from 'vue-router' 4 | import routes from './routers' 5 | import iView from 'iview' 6 | import { setTitle } from '@/libs/util' 7 | 8 | Vue.use(Router) 9 | const router = new Router({ 10 | routes, 11 | mode: 'history' 12 | }) 13 | 14 | router.beforeEach((to, from, next) => { 15 | iView.LoadingBar.start() 16 | const token = Cookies.get('token') 17 | if (token) { 18 | next() 19 | } else { 20 | if (to.name === 'login') next() 21 | else next({ name: 'login' }) 22 | } 23 | }) 24 | 25 | router.afterEach(to => { 26 | setTitle(to, router.app) 27 | iView.LoadingBar.finish() 28 | window.scrollTo(0, 0) 29 | }) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /frontend/src/static/base.css: -------------------------------------------------------------------------------- 1 | .modalcontent { 2 | max-height:300px; 3 | overflow-y:auto 4 | } 5 | 6 | .formcontent { 7 | max-height:400px; 8 | overflow-y:auto 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import user from './module/user' 5 | import app from './module/app' 6 | 7 | Vue.use(Vuex) 8 | 9 | export default new Vuex.Store({ 10 | state: { 11 | // 12 | }, 13 | mutations: { 14 | // 15 | }, 16 | actions: { 17 | // 18 | }, 19 | modules: { 20 | user, 21 | app 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /frontend/src/view/argu-page/params.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /frontend/src/view/argu-page/query.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /frontend/src/view/components/cropper/cropper.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 43 | 44 | 49 | -------------------------------------------------------------------------------- /frontend/src/view/components/drag-drawer/index.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 88 | 89 | 98 | -------------------------------------------------------------------------------- /frontend/src/view/components/editor/editor.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /frontend/src/view/components/icons/icons.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 55 | 56 | 64 | -------------------------------------------------------------------------------- /frontend/src/view/components/markdown/markdown.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /frontend/src/view/components/org-tree/components/zoom-controller.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 48 | 49 | 82 | -------------------------------------------------------------------------------- /frontend/src/view/components/org-tree/index.less: -------------------------------------------------------------------------------- 1 | @wrapper: ~'department'; 2 | .percent-100 { 3 | width: 100%; 4 | height: 100%; 5 | } 6 | .@{wrapper}-outer { 7 | .percent-100; 8 | overflow: hidden; 9 | .tip-box{ 10 | position: absolute; 11 | left: 20px; 12 | top: 20px; 13 | z-index: 12; 14 | } 15 | .zoom-box { 16 | position: absolute; 17 | right: 30px; 18 | bottom: 30px; 19 | z-index: 2; 20 | } 21 | .view-box { 22 | position: absolute; 23 | top: 0; 24 | bottom: 0; 25 | left: 0; 26 | right: 0; 27 | z-index: 1; 28 | cursor: move; 29 | .org-tree-drag-wrapper { 30 | width: 100%; 31 | height: 100%; 32 | } 33 | .org-tree-wrapper { 34 | display: inline-block; 35 | position: absolute; 36 | left: 50%; 37 | top: 50%; 38 | transition: transform 0.2s ease-out; 39 | .org-tree-node-label { 40 | box-shadow: 0px 2px 12px 0px rgba(143, 154, 165, 0.4); 41 | border-radius: 4px; 42 | .org-tree-node-label-inner { 43 | padding: 0; 44 | .custom-org-node { 45 | padding: 14px 41px; 46 | background: #738699; 47 | user-select: none; 48 | word-wrap: none; 49 | white-space: nowrap; 50 | border-radius: 4px; 51 | color: #ffffff; 52 | font-size: 14px; 53 | font-weight: 500; 54 | line-height: 20px; 55 | transition: background 0.1s ease-in; 56 | cursor: default; 57 | &:hover { 58 | background: #5d6c7b; 59 | transition: background 0.1s ease-in; 60 | } 61 | &.has-children-label { 62 | cursor: pointer; 63 | } 64 | .context-menu{ 65 | position: absolute; 66 | right: -10px; 67 | bottom: 20px; 68 | z-index: 10; 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /frontend/src/view/components/org-tree/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 74 | 75 | 77 | -------------------------------------------------------------------------------- /frontend/src/view/components/public/copyright.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /frontend/src/view/components/split-pane/split-pane.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 40 | 41 | 80 | -------------------------------------------------------------------------------- /frontend/src/view/components/tables/tables.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 68 | 69 | 72 | -------------------------------------------------------------------------------- /frontend/src/view/components/tree-select/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 87 | 88 | 91 | -------------------------------------------------------------------------------- /frontend/src/view/error-page/401.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /frontend/src/view/error-page/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /frontend/src/view/error-page/500.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | -------------------------------------------------------------------------------- /frontend/src/view/error-page/back-btn-group.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 39 | -------------------------------------------------------------------------------- /frontend/src/view/error-page/error-content.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | -------------------------------------------------------------------------------- /frontend/src/view/error-page/error.less: -------------------------------------------------------------------------------- 1 | .error-page{ 2 | width: 100%; 3 | height: 100%; 4 | position: relative; 5 | background: #f8f8f9; 6 | .content-con{ 7 | width: 700px; 8 | height: 600px; 9 | position: absolute; 10 | left: 50%; 11 | top: 50%; 12 | transform: translate(-50%, -60%); 13 | img{ 14 | display: block; 15 | width: 100%; 16 | height: 100%; 17 | } 18 | .text-con{ 19 | position: absolute; 20 | left: 0px; 21 | top: 0px; 22 | h4{ 23 | position: absolute; 24 | left: 0px; 25 | top: 0px; 26 | font-size: 80px; 27 | font-weight: 700; 28 | color: #348EED; 29 | } 30 | h5{ 31 | position: absolute; 32 | width: 700px; 33 | left: 0px; 34 | top: 100px; 35 | font-size: 20px; 36 | font-weight: 700; 37 | color: #67647D; 38 | } 39 | } 40 | .back-btn-group{ 41 | position: absolute; 42 | right: 0px; 43 | bottom: 20px; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/view/error-store/error-store.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /frontend/src/view/excel/common.less: -------------------------------------------------------------------------------- 1 | .margin-top-8{ 2 | margin-top: 8px; 3 | } 4 | .margin-top-10{ 5 | margin-top: 10px; 6 | } 7 | .margin-top-20{ 8 | margin-top: 20px; 9 | } 10 | .margin-left-10{ 11 | margin-left: 10px; 12 | } 13 | .margin-bottom-10{ 14 | margin-bottom: 10px; 15 | } 16 | .margin-bottom-100{ 17 | margin-bottom: 100px; 18 | } 19 | .margin-right-10{ 20 | margin-right: 10px; 21 | } 22 | .padding-left-6{ 23 | padding-left: 6px; 24 | } 25 | .padding-left-8{ 26 | padding-left: 5px; 27 | } 28 | .padding-left-10{ 29 | padding-left: 10px; 30 | } 31 | .padding-left-20{ 32 | padding-left: 20px; 33 | } 34 | .height-100{ 35 | height: 100%; 36 | } 37 | .height-120px{ 38 | height: 100px; 39 | } 40 | .height-200px{ 41 | height: 200px; 42 | } 43 | .height-492px{ 44 | height: 492px; 45 | } 46 | .height-460px{ 47 | height: 460px; 48 | } 49 | .line-gray{ 50 | height: 0; 51 | border-bottom: 2px solid #dcdcdc; 52 | } 53 | .notwrap{ 54 | word-break:keep-all; 55 | white-space:nowrap; 56 | overflow: hidden; 57 | text-overflow: ellipsis; 58 | } 59 | .padding-left-5{ 60 | padding-left: 10px; 61 | } 62 | [v-cloak]{ 63 | display: none; 64 | } -------------------------------------------------------------------------------- /frontend/src/view/excel/export-excel.vue: -------------------------------------------------------------------------------- 1 | 4 | 16 | 82 | -------------------------------------------------------------------------------- /frontend/src/view/i18n/i18n-page.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 40 | 41 | 51 | -------------------------------------------------------------------------------- /frontend/src/view/join-page.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 48 | 67 | -------------------------------------------------------------------------------- /frontend/src/view/login/login.less: -------------------------------------------------------------------------------- 1 | .login{ 2 | width: 100%; 3 | height: 100%; 4 | background-image: url('../../assets/images/login-bg.jpg'); 5 | background-size: cover; 6 | background-position: center; 7 | position: relative; 8 | &-con{ 9 | position: absolute; 10 | right: 160px; 11 | top: 50%; 12 | transform: translateY(-60%); 13 | width: 300px; 14 | &-header{ 15 | font-size: 16px; 16 | font-weight: 300; 17 | text-align: center; 18 | padding: 30px 0; 19 | } 20 | .form-con{ 21 | padding: 10px 0 0; 22 | } 23 | .login-tip{ 24 | font-size: 10px; 25 | text-align: center; 26 | color: #c3c3c3; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/src/view/login/login.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | 18 | 75 | 76 | 78 | -------------------------------------------------------------------------------- /frontend/src/view/multilevel/level-2-1.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /frontend/src/view/multilevel/level-2-2/level-2-2-1.vue: -------------------------------------------------------------------------------- 1 | 7 | 17 | -------------------------------------------------------------------------------- /frontend/src/view/multilevel/level-2-2/level-2-2-2.vue: -------------------------------------------------------------------------------- 1 | 7 | 17 | -------------------------------------------------------------------------------- /frontend/src/view/multilevel/level-2-3.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /frontend/src/view/projects/category/Console.vue: -------------------------------------------------------------------------------- 1 | 4 | 52 | -------------------------------------------------------------------------------- /frontend/src/view/projects/category/Xterm.js: -------------------------------------------------------------------------------- 1 | import { Terminal } from 'xterm' 2 | import * as fit from 'xterm/lib/addons/fit/fit' 3 | import * as attach from 'xterm/lib/addons/attach/attach' 4 | Terminal.applyAddon(fit) 5 | Terminal.applyAddon(attach) 6 | 7 | export default Terminal -------------------------------------------------------------------------------- /frontend/src/view/projects/category/webssh_detail.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 49 | 50 | 51 | 53 | -------------------------------------------------------------------------------- /frontend/src/view/single-page/error-logger.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 88 | 89 | 92 | -------------------------------------------------------------------------------- /frontend/src/view/single-page/home/example.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 115 | -------------------------------------------------------------------------------- /frontend/src/view/single-page/home/index.js: -------------------------------------------------------------------------------- 1 | import home from './home.vue' 2 | export default home 3 | -------------------------------------------------------------------------------- /frontend/src/view/tools-methods/tools-methods.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 78 | 79 | 82 | -------------------------------------------------------------------------------- /frontend/src/view/update/update-paste.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 62 | 63 | 78 | -------------------------------------------------------------------------------- /frontend/src/view/update/update-table.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 47 | 48 | 56 | -------------------------------------------------------------------------------- /frontend/tests/e2e/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "cypress" 4 | ], 5 | "env": { 6 | "mocha": true, 7 | "cypress/globals": true 8 | }, 9 | "rules": { 10 | "strict": "off" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/guides/guides/plugins-guide.html 2 | 3 | module.exports = (on, config) => Object.assign({}, config, { 4 | fixturesFolder: 'tests/e2e/fixtures', 5 | integrationFolder: 'tests/e2e/specs', 6 | screenshotsFolder: 'tests/e2e/screenshots', 7 | videosFolder: 'tests/e2e/videos', 8 | supportFile: 'tests/e2e/support/index.js' 9 | }) 10 | -------------------------------------------------------------------------------- /frontend/tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe('My First Test', () => { 4 | it('Visits the app root url', () => { 5 | cy.visit('/') 6 | cy.contains('h1', 'Welcome to Your Vue.js App') 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /frontend/tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /frontend/tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /frontend/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true 4 | }, 5 | rules: { 6 | 'import/no-extraneous-dependencies': 'off' 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/tests/unit/HelloWorld.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { shallow } from '@vue/test-utils' 3 | import HelloWorld from '@/components/HelloWorld.vue' 4 | 5 | describe('HelloWorld.vue', () => { 6 | it('renders props.msg when passed', () => { 7 | const msg = 'new message' 8 | const wrapper = shallow(HelloWorld, { 9 | propsData: { msg } 10 | }) 11 | expect(wrapper.text()).to.include(msg) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const resolve = dir => { 4 | return path.join(__dirname, dir) 5 | } 6 | 7 | // 项目部署基础 8 | // 默认情况下,我们假设你的应用将被部署在域的根目录下, 9 | // 例如:https://www.my-app.com/ 10 | // 默认:'/' 11 | // 如果您的应用程序部署在子路径中,则需要在这指定子路径 12 | // 例如:https://www.foobar.com/my-app/ 13 | // 需要将它改为'/my-app/' 14 | // iview-admin线上演示打包路径: https://file.iviewui.com/admin-dist/ 15 | const BASE_URL = process.env.NODE_ENV === 'production' 16 | ? '/' 17 | : '/' 18 | 19 | module.exports = { 20 | // Project deployment base 21 | // By default we assume your app will be deployed at the root of a domain, 22 | // e.g. https://www.my-app.com/ 23 | // If your app is deployed at a sub-path, you will need to specify that 24 | // sub-path here. For example, if your app is deployed at 25 | // https://www.foobar.com/my-app/ 26 | // then change this to '/my-app/' 27 | baseUrl: BASE_URL, 28 | // tweak internal webpack configuration. 29 | // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md 30 | // 如果你不需要使用eslint,把lintOnSave设为false即可 31 | lintOnSave: true, 32 | chainWebpack: config => { 33 | config.resolve.alias 34 | .set('@', resolve('src')) // key,value自行定义,比如.set('@@', resolve('src/components')) 35 | .set('_c', resolve('src/components')) 36 | }, 37 | // 设为false打包时不生成.map文件 38 | productionSourceMap: false 39 | // 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串 40 | // devServer: { 41 | // proxy: 'localhost:3000' 42 | // } 43 | } 44 | -------------------------------------------------------------------------------- /images/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/images/dashboard.png -------------------------------------------------------------------------------- /images/idc-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/images/idc-detail.png -------------------------------------------------------------------------------- /images/idc-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/images/idc-list.png -------------------------------------------------------------------------------- /images/log-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/images/log-detail.png -------------------------------------------------------------------------------- /images/log-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/images/log-list.png -------------------------------------------------------------------------------- /images/server-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/images/server-detail.png -------------------------------------------------------------------------------- /images/server-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/images/server-list.png -------------------------------------------------------------------------------- /images/server-ssh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/myide/open-cmdb/68bc028d5d6162dbfa724d7bbf17363f65e44557/images/server-ssh.png --------------------------------------------------------------------------------