├── 996License ├── Dockerfile ├── LICENSE ├── README.md ├── img ├── PER.png ├── audit.png ├── dash.png ├── highlight.png ├── login.png ├── logo.jpg ├── myorder.png └── query.png ├── install ├── inception.tar └── yearning-docker-compose │ ├── docker-compose.yml │ ├── docker │ └── etc │ │ └── mysql │ │ └── my.cnf │ └── init-sql │ └── install.sql ├── logo.png ├── runserver.py ├── src ├── .gitignore ├── core │ ├── __init__.py │ ├── api │ │ ├── __init__.py │ │ ├── applygrained.py │ │ ├── auditorder.py │ │ ├── authgroup.py │ │ ├── dashboard.py │ │ ├── general.py │ │ ├── managerdb.py │ │ ├── myorder.py │ │ ├── osc.py │ │ ├── record.py │ │ ├── serachsql.py │ │ ├── setting.py │ │ ├── sqlorder.py │ │ └── user.py │ ├── apps.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ └── task.py ├── deploy.conf.template ├── exportData │ └── .gitkeep ├── gunicorn.conf ├── libs │ ├── __init__.py │ ├── baseview.py │ ├── call_inception.py │ ├── con_database.py │ ├── cryptoAES.py │ ├── logo.png │ ├── rollback.py │ ├── send_email.py │ ├── serializers.py │ └── util.py ├── log │ └── .gitkeep ├── manage.py ├── requirements.txt └── settingConf │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── webpage ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.test.conf.js ├── config ├── dev.env.js ├── env.js ├── index.js ├── prod.env.js └── test.env.js ├── index.html ├── package.json ├── src ├── aspsm.js ├── assets │ ├── alipay.jpg │ ├── avatar.png │ ├── tablesmargintop.css │ └── wechat.jpg ├── components │ ├── assistantManger │ │ ├── queryRecord.vue │ │ └── record.vue │ ├── audit │ │ ├── expend.vue │ │ ├── permissions.vue │ │ ├── queryAudit.vue │ │ └── sqlAudit.vue │ ├── error │ │ ├── 401.css │ │ ├── 401.less │ │ ├── 401.vue │ │ ├── 404.css │ │ ├── 404.less │ │ ├── 404.vue │ │ ├── 500.css │ │ ├── 500.less │ │ └── 500.vue │ ├── home │ │ ├── components │ │ │ ├── countUp.vue │ │ │ ├── dataSourcePie.vue │ │ │ ├── inforCard.vue │ │ │ ├── styles │ │ │ │ ├── infor-card.css │ │ │ │ ├── infor-card.less │ │ │ │ ├── to-do-list-item.css │ │ │ │ └── to-do-list-item.less │ │ │ └── toDoListItem.vue │ │ ├── home.css │ │ ├── home.less │ │ └── home.vue │ ├── management │ │ ├── authGroup.vue │ │ ├── databaseManager.vue │ │ ├── setting.vue │ │ └── userInfo.vue │ ├── order │ │ ├── components │ │ │ ├── myorderList.vue │ │ │ ├── table.css │ │ │ └── table.less │ │ ├── ddlOrder.vue │ │ ├── dmlOrder.vue │ │ └── myOrder.vue │ ├── personalCenter │ │ ├── own-space.css │ │ ├── own-space.less │ │ └── own-space.vue │ └── query │ │ ├── querySql.vue │ │ ├── submitPage.vue │ │ └── workFlow.vue ├── libs │ ├── editor.vue │ └── util.js ├── login.vue ├── main.css ├── main.js ├── main.less ├── main.vue ├── main_components │ ├── breadcrumbNav.vue │ ├── locking-page.vue │ ├── sidebarMenu.vue │ ├── sidebarMenuShrink.vue │ ├── tagsPageOpened.vue │ ├── unlock.css │ ├── unlock.less │ └── unlock.vue ├── router.js ├── styles │ ├── common.css │ ├── common.less │ └── simplemde.min.css └── subnet.vue ├── static ├── .gitkeep ├── avatar.png ├── icon.png └── particlesjs-config.json └── test ├── e2e ├── custom-assertions │ └── elementCount.js ├── nightwatch.conf.js ├── runner.js └── specs │ └── test.js └── unit ├── .eslintrc ├── index.js ├── karma.conf.js └── specs └── Hello.spec.js /996License: -------------------------------------------------------------------------------- 1 | Copyright (c) <2019> 2 | 3 | 996 License Version 1.0 (Draft) 4 | 5 | Permission is hereby granted to any individual or legal entity 6 | obtaining a copy of this licensed work (including the source code, 7 | documentation and/or related items, hereinafter collectively referred 8 | to as the "licensed work"), free of charge, to deal with the licensed 9 | work for any purpose, including without limitation, the rights to use, 10 | reproduce, modify, prepare derivative works of, distribute, publish 11 | and sublicense the licensed work, subject to the following conditions: 12 | 13 | 1. The individual or the legal entity must conspicuously display, 14 | without modification, this License and the notice on each redistributed 15 | or derivative copy of the Licensed Work. 16 | 17 | 2. The individual or the legal entity must strictly comply with all 18 | applicable laws, regulations, rules and standards of the jurisdiction 19 | relating to labor and employment where the individual is physically 20 | located or where the individual was born or naturalized; or where the 21 | legal entity is registered or is operating (whichever is stricter). In 22 | case that the jurisdiction has no such laws, regulations, rules and 23 | standards or its laws, regulations, rules and standards are 24 | unenforceable, the individual or the legal entity are required to 25 | comply with Core International Labor Standards. 26 | 27 | 3. The individual or the legal entity shall not induce or force its 28 | employee(s), whether full-time or part-time, or its independent 29 | contractor(s), in any methods, to agree in oral or written form, to 30 | directly or indirectly restrict, weaken or relinquish his or her 31 | rights or remedies under such laws, regulations, rules and standards 32 | relating to labor and employment as mentioned above, no matter whether 33 | such written or oral agreement are enforceable under the laws of the 34 | said jurisdiction, nor shall such individual or the legal entity 35 | limit, in any methods, the rights of its employee(s) or independent 36 | contractor(s) from reporting or complaining to the copyright holder or 37 | relevant authorities monitoring the compliance of the license about 38 | its violation(s) of the said license. 39 | 40 | THE LICENSED WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 41 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 42 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 43 | IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, 44 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 45 | OTHERWISE, ARISING FROM, OUT OF OR IN ANY WAY CONNECTION WITH THE 46 | LICENSED WORK OR THE USE OR OTHER DEALINGS IN THE LICENSED WORK. 47 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM registry.cn-hangzhou.aliyuncs.com/cookie/yearning_basic:latest 2 | 3 | LABEL maintainer="HenryYee-2019/07/29" 4 | 5 | EXPOSE 8000 6 | 7 | ADD src /mnt/src 8 | 9 | WORKDIR /mnt/src 10 | 11 | RUN mv deploy.conf.template deploy.conf 12 | 13 | CMD ["/bin/bash" "/usr/local/bin/start_yearning.sh"] 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | # Yearning SQL审核平台 6 | ![LICENSE](https://img.shields.io/badge/license-AGPL%20-blue.svg) 7 | ![](https://img.shields.io/badge/build-release-brightgreen.svg)   8 | ![](https://img.shields.io/badge/version-v1.4.7-brightgreen.svg)   9 | 10 | ##### MYSQL web端 SQL审核平台。 11 | 12 | ## Version 版本 13 | 14 | Yearning for Python v1.4.7 15 | 16 | ## Yearning2.0 前端项目 17 | 18 | [Gemini](https://github.com/cookieY/Gemini) 19 | 20 | ## Website 官网 21 | 22 | [www.yearning.io](http://yearning.io) 23 | 24 | 25 | ## Community 社区 26 | Yearning 使用与交流 Q群: 103674679 (已放弃维护,不适用于生产环境) 27 | 28 | Yearning2.0(Go) 使用交流 Q群: 747364310 29 | 30 | ## Feature 功能 31 | 32 | - SQL查询 33 | - 查询工单 34 | - 导出 35 | - 自动补全,智能提示 36 | - 查询语句审计 37 | - SQL审核 38 | - 流程化工单 39 | - SQL语句检测与执行 40 | - SQL回滚 41 | - 历史审核记录 42 | - 推送 43 | - E-mail工单推送 44 | - 钉钉webhook机器人工单推送 45 | - 用户权限及管理 46 | - 角色划分 47 | - 基于用户的细粒度权限 48 | - 注册 49 | - 其他 50 | - todoList 51 | - LDAP登录 52 | - 动态审核规则配置 53 | 54 | ## Install 安装及使用日志 55 | 56 | [使用及安装文档](http://guide.yearning.io) 57 | 58 | ## About 联系方式 59 | 60 |   E-mail: im@supermancookie.com 61 | 62 | ## Snapshot 效果展示 63 | 64 | - Login 65 | 66 | 67 | 68 | ![login](img/login.png) 69 | 70 | 71 | - Dashboard 72 | 73 | ![](img/dash.png) 74 | 75 | - 审核 76 | 77 | ![](img/audit.png) 78 | 79 | - SQL语法高亮及自动补全 80 | 81 | ![](img/highlight.png) 82 | 83 | - 查询 84 | 85 | ![](img/query.png) 86 | 87 | - 细粒度的权限分配 88 | ![](img/per.png) 89 | 90 | - 我的工单 91 | ![](img/myorder.png) 92 | 93 | 94 | ## License 95 | 96 | - AGPL v3 97 | 98 | 任何二次开发及二次开源项目请严格遵守相应开源许可 99 | 100 | 2019 © Henry Yee 101 | 102 | 103 | -------------------------------------------------------------------------------- /img/PER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/img/PER.png -------------------------------------------------------------------------------- /img/audit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/img/audit.png -------------------------------------------------------------------------------- /img/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/img/dash.png -------------------------------------------------------------------------------- /img/highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/img/highlight.png -------------------------------------------------------------------------------- /img/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/img/login.png -------------------------------------------------------------------------------- /img/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/img/logo.jpg -------------------------------------------------------------------------------- /img/myorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/img/myorder.png -------------------------------------------------------------------------------- /img/query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/img/query.png -------------------------------------------------------------------------------- /install/inception.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/install/inception.tar -------------------------------------------------------------------------------- /install/yearning-docker-compose/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | db: 5 | image: mysql:5.7 6 | volumes: 7 | - ./docker/etc/mysql/:/etc/mysql/conf.d/ 8 | - ./db_data/:/var/lib/mysql/ 9 | - ./init-sql/:/docker-entrypoint-initdb.d/ 10 | restart: always 11 | ports: 12 | - "3306:3306" 13 | environment: 14 | MYSQL_ROOT_PASSWORD: yearning 15 | MYSQL_DATABASE: Yearning 16 | MYSQL_USER: yearning 17 | MYSQL_PASSWORD: yearning 18 | yearning: 19 | image: registry.cn-hangzhou.aliyuncs.com/cookie/yearning:latest 20 | depends_on: 21 | - db 22 | ports: 23 | - "8080:8000" 24 | environment: 25 | HOST: localhost 26 | MYSQL_PASSWORD: yearning 27 | MYSQL_USER: root 28 | MYSQL_ADDR: db 29 | 30 | # 默认账号:admin,默认密码:Yearning_admin 31 | # 感谢 eacdy 张功震 贡献 32 | -------------------------------------------------------------------------------- /install/yearning-docker-compose/docker/etc/mysql/my.cnf: -------------------------------------------------------------------------------- 1 | [client] 2 | default-character-set = utf8 3 | 4 | [mysql] 5 | default-character-set = utf8 6 | 7 | [mysqld] 8 | 9 | character-set-client-handshake = FALSE 10 | character-set-server = utf8 11 | collation-server = utf8_unicode_ci 12 | init_connect='SET NAMES utf8' -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/logo.png -------------------------------------------------------------------------------- /runserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from multiprocessing import Process 3 | import subprocess 4 | import os 5 | import configparser 6 | 7 | _conf = configparser.ConfigParser() 8 | _conf.read('src/deploy.conf') 9 | OutIp = _conf.get('host', 'ipaddress') 10 | BASEPATH = os.path.dirname(os.path.abspath(__file__)) 11 | 12 | def startdjango(): 13 | os.chdir(os.path.join(BASEPATH, 'src')) 14 | subprocess.call('python3 manage.py runserver 0.0.0.0:8000', shell=True) 15 | 16 | def startnode(): 17 | os.chdir(os.path.join(BASEPATH, 'webpage')) 18 | subprocess.call('npm run dev', shell=True) 19 | 20 | def main(): 21 | print('请访问%s'%OutIp) 22 | django = Process(target=startdjango, args=()) 23 | node = Process(target=startnode, args=()) 24 | django.start() 25 | node.start() 26 | 27 | django.join() 28 | node.join() 29 | 30 | if __name__ == "__main__": 31 | main() 32 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/* 3 | log/*.log 4 | deploy.conf 5 | *.py[cod] 6 | *.so 7 | *.egg 8 | *.egg-info 9 | __pycache__/* 10 | -------------------------------------------------------------------------------- /src/core/__init__.py: -------------------------------------------------------------------------------- 1 | import pymysql 2 | 3 | 4 | pymysql.install_as_MySQLdb() 5 | 6 | # This will make sure the app is always imported when 7 | # Django starts so that shared_task will use this app. -------------------------------------------------------------------------------- /src/core/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/src/core/api/__init__.py -------------------------------------------------------------------------------- /src/core/api/applygrained.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import threading 4 | import ast 5 | from libs import baseview, send_email, util 6 | from django.http import HttpResponse 7 | from django.db import transaction 8 | from rest_framework.response import Response 9 | from core.models import Account, applygrained, globalpermissions 10 | 11 | CUSTOM_ERROR = logging.getLogger('Yearning.core.views') 12 | 13 | 14 | class audit_grained(baseview.SuperUserpermissions): 15 | 16 | def get(self, request, args: str = None): 17 | 18 | user_id = Account.objects.filter(username=request.user).first().id 19 | page = request.GET.get('page') 20 | if user_id == 1: 21 | pn = applygrained.objects.count() 22 | start = int(page) * 10 - 10 23 | end = int(page) * 10 24 | user_list = applygrained.objects.all().order_by('-id')[start:end] 25 | ser = [] 26 | for i in user_list: 27 | ser.append( 28 | {'work_id': i.work_id, 'status': i.status, 'username': i.username, 29 | 'permissions': i.permissions, 'auth_group': i.auth_group, 30 | 'real_name': i.real_name} 31 | ) 32 | return Response({'data': ser, 'pn': pn}) 33 | 34 | else: 35 | return Response([]) 36 | 37 | def post(self, request, args: str = None): 38 | 39 | user = request.data['user'] 40 | work_id = request.data['work_id'] 41 | if request.data['status'] == 0: 42 | try: 43 | auth_group = request.data['auth_group'] 44 | except KeyError as e: 45 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 46 | return HttpResponse(status=500) 47 | else: 48 | with transaction.atomic(): 49 | Account.objects.filter(username=user).update( 50 | auth_group=auth_group) 51 | applygrained.objects.filter( 52 | work_id=work_id).update(status=1) 53 | mail = Account.objects.filter(username=user).first() 54 | thread = threading.Thread(target=push_message, args=( 55 | {'to_user': user, 'workid': work_id}, 3, user, mail.email, work_id, '同意')) 56 | thread.start() 57 | return Response('权限已更新成功!') 58 | else: 59 | applygrained.objects.filter(work_id=work_id).update(status=0) 60 | mail = Account.objects.filter(username=user).first() 61 | thread = threading.Thread(target=push_message, 62 | args=({'to_user': user, 'workid': work_id}, 63 | 4, user, mail.email, work_id, '驳回')) 64 | thread.start() 65 | return Response('权限已驳回!') 66 | 67 | def put(self, request, args: str = None): 68 | 69 | work_id_list = json.loads(request.data['work_id']) 70 | for i in work_id_list: 71 | applygrained.objects.filter(work_id=i).delete() 72 | return Response('申请记录已删除!') 73 | 74 | 75 | class apply_grained(baseview.BaseView): 76 | 77 | def post(self, request, args: str = None): 78 | 79 | authgroup_str = (",".join(request.data['auth_group'])) 80 | grained_list = json.loads(request.data['grained_list']) 81 | real_name = request.data['real_name'] 82 | work_id = util.workId() 83 | applygrained.objects.get_or_create( 84 | work_id=work_id, 85 | username=request.user, 86 | permissions=grained_list, 87 | auth_group=authgroup_str, 88 | status=2, 89 | real_name=real_name) 90 | mail = Account.objects.filter(id=1).first() 91 | try: 92 | thread = threading.Thread(target=push_message, args=( 93 | {'to_user': request.user, 'workid': work_id}, 94 | 2, request.user, mail.email, work_id, '已提交')) 95 | thread.start() 96 | except Exception as e: 97 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 98 | 99 | finally: 100 | return Response('权限申请已提交!') 101 | 102 | 103 | def push_message(message=None, type=None, user=None, to_addr=None, work_id=None, status=None): 104 | try: 105 | tag = globalpermissions.objects.filter(authorization='global').first() 106 | if tag.message['mail']: 107 | try: 108 | put_mess = send_email.send_email(to_addr=to_addr) 109 | put_mess.send_mail(mail_data=message, type=type) 110 | except Exception as e: 111 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 112 | 113 | if tag.message['ding']: 114 | un_init = util.init_conf() 115 | webhook = ast.literal_eval(un_init['message']) 116 | util.dingding(content='权限申请通知\n工单编号:%s\n发起人:%s\n状态:%s' % (work_id, user, status), 117 | url=webhook['webhook']) 118 | except Exception as e: 119 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 120 | -------------------------------------------------------------------------------- /src/core/api/dashboard.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | from libs import baseview 4 | from rest_framework.response import Response 5 | from django.http import HttpResponse 6 | from core.models import ( 7 | SqlOrder, 8 | Account, 9 | DatabaseList, 10 | Todolist 11 | ) 12 | from libs.serializers import ( 13 | UserINFO 14 | ) 15 | from core.task import set_auth_group 16 | 17 | CUSTOM_ERROR = logging.getLogger('Yearning.core.views') 18 | 19 | 20 | class dashboard(baseview.BaseView): 21 | ''' 22 | 23 | :argument 主页面展示数据接口api 24 | 25 | get 主页图表信息 26 | 27 | put todo列表 删除todo 个人信息 28 | 29 | post todo提交 30 | 31 | ''' 32 | 33 | def get(self, request, args=None): 34 | if args == 'pie': 35 | try: 36 | alter = SqlOrder.objects.filter( 37 | type=0, username=request.user).count() 38 | sql = SqlOrder.objects.filter( 39 | type=1, username=request.user).count() 40 | return Response([alter, sql]) 41 | except Exception as e: 42 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 43 | return HttpResponse(status=500) 44 | 45 | elif args == 'infocard': 46 | try: 47 | user = Account.objects.count() 48 | order = SqlOrder.objects.filter(username=request.user).count() 49 | link = DatabaseList.objects.count() 50 | return Response([user, order, link]) 51 | except Exception as e: 52 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 53 | return HttpResponse(status=500) 54 | 55 | elif args == 'messages': 56 | try: 57 | statement = Account.objects.filter( 58 | username=request.user).first() 59 | if statement.id == 1: 60 | return Response({'statement': statement.last_name}) 61 | else: 62 | return Response({'statement': 'pass'}) 63 | except Exception as e: 64 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 65 | return HttpResponse(status=500) 66 | 67 | elif args == 'menu': 68 | 69 | permissions = set_auth_group(request.user) 70 | return Response(json.dumps(permissions)) 71 | 72 | def put(self, request, args=None): 73 | 74 | if args == 'todolist': 75 | try: 76 | todo = Todolist.objects.filter(username=request.user).all() 77 | return Response([{'title': i.content} for i in todo]) 78 | except Exception as e: 79 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 80 | return HttpResponse(status=500) 81 | 82 | elif args == 'deltodo': 83 | try: 84 | todo = request.data['todo'] 85 | except KeyError as e: 86 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 87 | return HttpResponse(status=500) 88 | else: 89 | try: 90 | Todolist.objects.filter( 91 | username=request.user, content=todo).delete() 92 | return Response('') 93 | except Exception as e: 94 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 95 | return HttpResponse(status=500) 96 | 97 | elif args == 'ownspace': 98 | info = Account.objects.filter(username=request.user).get() 99 | _serializers = UserINFO(info) 100 | permissions = set_auth_group(request.user) 101 | return Response({'userinfo': _serializers.data, 'permissons': permissions}) 102 | 103 | elif args == 'statement': 104 | Account.objects.filter( 105 | username=request.user).update(last_name='pass') 106 | return Response('') 107 | 108 | def post(self, request, args=None): 109 | try: 110 | todo = request.data['todo'] 111 | except Exception as e: 112 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 113 | else: 114 | try: 115 | Todolist.objects.get_or_create( 116 | username=request.user, content=todo) 117 | return Response('') 118 | except Exception as e: 119 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 120 | return HttpResponse(status=500) 121 | -------------------------------------------------------------------------------- /src/core/api/myorder.py: -------------------------------------------------------------------------------- 1 | import logging, json, ast 2 | from libs import baseview, util 3 | from core.models import SqlOrder 4 | from django.http import HttpResponse 5 | from rest_framework.response import Response 6 | 7 | CUSTOM_ERROR = logging.getLogger('Yearning.core.views') 8 | 9 | 10 | class order(baseview.BaseView): 11 | ''' 12 | 13 | :argument 我的工单展示接口api 14 | 15 | ''' 16 | 17 | def get(self, request, args: str = None): 18 | try: 19 | page = request.GET.get('page') 20 | qurey = json.loads(request.GET.get('query')) 21 | un_init = util.init_conf() 22 | custom_com = ast.literal_eval(un_init['other']) 23 | except KeyError as e: 24 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 25 | else: 26 | try: 27 | start = (int(page) - 1) * 20 28 | end = int(page) * 20 29 | if qurey['valve']: 30 | if qurey['picker'][0] is '': 31 | info = SqlOrder.objects.filter(username=request.user, text__contains=qurey['text']).order_by( 32 | '-id').defer('sql')[start:end] 33 | 34 | page_number = SqlOrder.objects.filter(username=request.user, 35 | text__contains=qurey['text']).only('id').count() 36 | else: 37 | picker = [] 38 | for i in qurey['picker']: 39 | picker.append(i) 40 | info = SqlOrder.objects.filter(username=request.user, text__contains=qurey['text'], 41 | date__gte=picker[0], date__lte=picker[1]).defer('sql').order_by( 42 | '-id')[ 43 | start:end] 44 | 45 | page_number = SqlOrder.objects.filter(username=request.user, 46 | text__contains=qurey['text']).only('id').count() 47 | else: 48 | info = SqlOrder.objects.filter(username=request.user).defer('sql').order_by('-id')[start:end] 49 | page_number = SqlOrder.objects.filter(username=request.user).only('id').count() 50 | 51 | data = util.ser(info) 52 | return Response({'page': page_number, 'data': data, 'multi': custom_com['multi']}) 53 | except Exception as e: 54 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 55 | return HttpResponse(status=500) 56 | -------------------------------------------------------------------------------- /src/core/api/osc.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from django.http import HttpResponse 3 | from rest_framework.response import Response 4 | from libs import baseview, call_inception, con_database 5 | 6 | CUSTOM_ERROR = logging.getLogger('Yearning.core.views') 7 | 8 | 9 | class osc_step(baseview.SuperUserpermissions): 10 | 11 | def get(self, request, args: str = None): 12 | 13 | ''' 14 | 15 | :argument 根据获得的sha1,返回对应sql的osc进度 16 | 17 | ''' 18 | 19 | try: 20 | with call_inception.Inception(LoginDic={ 21 | 'host': '', 22 | 'user': '', 23 | 'password': '', 24 | 'db': '', 25 | 'port': '' 26 | }) as f: 27 | data = f.oscstep(sql="inception get osc_percent '%s';" % args) 28 | return Response(data) 29 | except Exception as e: 30 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 31 | return HttpResponse(e) 32 | 33 | def delete(self, request, args: str = None): 34 | 35 | ''' 36 | 37 | :argument: 根据获得的SHA1, 终止对应sql的osc 并返回执行结果 38 | 39 | ''' 40 | 41 | try: 42 | with call_inception.Inception(LoginDic={ 43 | 'host': '', 44 | 'user': '', 45 | 'password': '', 46 | 'db': '', 47 | 'port': '' 48 | }) as f: 49 | f.oscstep(sql=f"inception stop alter '{args}';") 50 | return Response('osc已终止,请刷新后查看详细信息') 51 | except Exception as e: 52 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 53 | return HttpResponse(e) 54 | -------------------------------------------------------------------------------- /src/core/api/record.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | from libs import baseview, rollback, util 4 | from rest_framework.response import Response 5 | from django.http import HttpResponse 6 | from core.models import SqlOrder, SqlRecord 7 | from libs.serializers import Record 8 | 9 | CUSTOM_ERROR = logging.getLogger('Yearning.core.views') 10 | 11 | 12 | class record_order(baseview.SuperUserpermissions): 13 | ''' 14 | 15 | :argument 记录展示请求接口api 16 | 17 | :return 记录及记录总数 18 | 19 | ''' 20 | 21 | def get(self, request, args=None): 22 | try: 23 | page = request.GET.get('page') 24 | except KeyError as e: 25 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 26 | return HttpResponse(status=500) 27 | else: 28 | try: 29 | pagenumber = SqlOrder.objects.filter(status=1, assigned=request.user).count() 30 | start = (int(page) - 1) * 20 31 | end = int(page) * 20 32 | sql = SqlOrder.objects.raw( 33 | ''' 34 | select o.id,o.work_id,o.text,o.backup,o.date,o.assigned, 35 | o.username,o.real_name,o.basename,core_databaselist.connection_name, \ 36 | core_databaselist.computer_room from core_sqlorder as o \ 37 | INNER JOIN core_databaselist on \ 38 | o.bundle_id = core_databaselist.id where o.status = 1 and o.assigned = '%s'\ 39 | ORDER BY o.id desc 40 | ''' % request.user 41 | )[start:end] 42 | return Response({'data': util.ser(sql), 'page': pagenumber}) 43 | except Exception as e: 44 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 45 | return HttpResponse(status=500) 46 | 47 | 48 | class order_detail(baseview.BaseView): 49 | ''' 50 | 51 | :argument 执行工单的详细信息请求接口api 52 | 53 | ''' 54 | 55 | def get(self, request, args: str = None): 56 | 57 | ''' 58 | 59 | :argument 详细信息数据展示 60 | 61 | :param args: 根据获得的work_id status order_id 查找相关数据并返回 62 | 63 | :return: 64 | 65 | ''' 66 | try: 67 | work_id = request.GET.get('workid') 68 | status = request.GET.get('status') 69 | order_id = request.GET.get('id') 70 | except KeyError as e: 71 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 72 | else: 73 | type_id = SqlOrder.objects.filter(id=order_id).first() 74 | try: 75 | if status == '1' or status == '4': 76 | data = SqlRecord.objects.filter(workid=work_id).all() 77 | _serializers = Record(data, many=True) 78 | return Response({'data': _serializers.data, 'type': type_id.type}) 79 | else: 80 | data = SqlOrder.objects.filter(work_id=work_id).first() 81 | _in = {'data': [{'sql': x} for x in data.sql.split(';')], 'type': type_id.type} 82 | return Response(_in) 83 | except Exception as e: 84 | CUSTOM_ERROR.error(f'{e.__class__.__name__} : {e}') 85 | return HttpResponse(status=500) 86 | 87 | def put(self, request, args: str = None): 88 | 89 | ''' 90 | 91 | :argument 当工单驳回后重新提交功能接口api 92 | 93 | :param args: 根据获得order_id 返回对应被驳回的sql 94 | 95 | :return: 96 | 97 | ''' 98 | 99 | try: 100 | order_id = request.data['id'] 101 | except KeyError as e: 102 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 103 | else: 104 | try: 105 | info = SqlOrder.objects.raw( 106 | "select core_sqlorder.*,core_databaselist.connection_name,\ 107 | core_databaselist.computer_room from core_sqlorder INNER JOIN \ 108 | core_databaselist on core_sqlorder.bundle_id = core_databaselist.id \ 109 | WHERE core_sqlorder.id = %s" % order_id) 110 | data = util.ser(info) 111 | sql = data[0]['sql'].split(';') 112 | _tmp = '' 113 | for i in sql: 114 | _tmp += i + ";\n" 115 | return Response({'data': data[0], 'sql': _tmp.strip('\n'), 'type': 0}) 116 | except Exception as e: 117 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 118 | return HttpResponse(status=500) 119 | 120 | def post(self, request, args: str = None): 121 | 122 | ''' 123 | 124 | :argument 当工单执行后sql回滚功能接口api 125 | 126 | :param args: 根据获得order_id 返回对应的回滚sql 127 | 128 | :return: {'data': data[0], 'sql': rollback_sql, 'type': 1} 129 | 130 | ''' 131 | 132 | try: 133 | order_id = request.data['id'] 134 | tmp = list(set(json.loads(request.data['opid']))) 135 | info = [x for x in tmp if x.find("0_0_") == -1] 136 | except KeyError as e: 137 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 138 | return HttpResponse(status=500) 139 | else: 140 | try: 141 | sql = [] 142 | rollback_sql = [] 143 | dataset = SqlOrder.objects.raw( 144 | "select core_sqlorder.*,core_databaselist.connection_name,\ 145 | core_databaselist.computer_room from core_sqlorder INNER JOIN \ 146 | core_databaselist on core_sqlorder.bundle_id = core_databaselist.id \ 147 | WHERE core_sqlorder.id = %s" 148 | % order_id) 149 | data = util.ser(dataset) 150 | for i in info: 151 | _data = SqlRecord.objects.filter(sequence=i).first() 152 | roll = rollback.rollbackSQL(db=_data.backup_dbname, opid=i) 153 | link = _data.backup_dbname + '.' + roll['tablename'] 154 | sql.append(rollback.roll(backdb=link, opid=i)) 155 | for i in sql: 156 | for c in i: 157 | rollback_sql.append(c['rollback_statement']) 158 | rollback_sql = sorted(rollback_sql) 159 | if rollback_sql == []: return HttpResponse(status=500) 160 | rollback_sql = [{'sql': x} for x in rollback_sql] 161 | return Response({'data': data[0], 'sql': rollback_sql, 'type': 1}) 162 | except Exception as e: 163 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 164 | return HttpResponse(status=500) 165 | -------------------------------------------------------------------------------- /src/core/api/setting.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | from libs import baseview, util 4 | from rest_framework.response import Response 5 | from core.models import globalpermissions, Account 6 | from django.http import HttpResponse 7 | 8 | CUSTOM_ERROR = logging.getLogger('Yearning.core.views') 9 | 10 | 11 | class setting_view(baseview.SuperUserpermissions): 12 | 13 | def get(self, request, args: str = None): 14 | user_id = Account.objects.filter(username=request.user).first().id 15 | if user_id == 1: 16 | setting = globalpermissions.objects.filter(authorization='global').first() 17 | return Response( 18 | { 19 | 'inception': dict(setting.inception), 20 | 'ldap': dict(setting.ldap), 21 | 'message': dict(setting.message), 22 | 'other': dict(setting.other) 23 | } 24 | ) 25 | else: 26 | return Response({'other': 'refused'}) 27 | 28 | def put(self, request, args: str = None): 29 | 30 | try: 31 | if args == '1': # ldap测试 32 | ldap = json.loads(request.data['ldap']) 33 | ldap_test = util.test_auth( 34 | url=ldap['url'], 35 | user=ldap['user'], 36 | password=ldap['password']) 37 | if ldap_test: 38 | return Response('ldap连接成功!') 39 | else: 40 | return Response('ldap连接失败!') 41 | elif args == '2': 42 | ding = request.data['ding'] 43 | util.dingding('yearning webhook测试', ding) 44 | return Response('已发送测试消息,请在钉钉中查看') 45 | 46 | else: 47 | mail = json.loads(request.data['mail']) 48 | import smtplib 49 | from email.utils import parseaddr, formataddr 50 | from email.mime.text import MIMEText 51 | from email.header import Header 52 | 53 | def _format_addr(s): 54 | name, addr = parseaddr(s) 55 | return formataddr((Header(name, 'utf-8').encode(), addr)) 56 | msg = MIMEText('Yearning test Message!', 'plain', 'utf-8') 57 | msg['From'] = _format_addr('Yearning_Admin <%s>' % mail['user']) 58 | msg['Subject'] = Header('Yearning 消息推送测试', 'utf-8').encode() 59 | if mail['ssl']: 60 | server = smtplib.SMTP_SSL(mail['smtp_host'], mail['smtp_port']) # SMTP协议默认端口是25 61 | else: 62 | server = smtplib.SMTP(mail['smtp_host'], mail['smtp_port']) # SMTP协议默认端口是25 63 | server.set_debuglevel(1) 64 | server.login(mail['user'], mail['password']) 65 | server.sendmail(mail['user'], [mail['to_user']], msg.as_string()) 66 | server.quit() 67 | return Response('已发送测试邮件,请注意查收!') 68 | except Exception as e: 69 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 70 | return HttpResponse(e) 71 | 72 | def post(self, request, args: str = None): 73 | 74 | try: 75 | inception = json.loads(request.data['inception']) 76 | ldap = json.loads(request.data['ldap']) 77 | message = json.loads(request.data['message']) 78 | other = json.loads(request.data['other']) 79 | except KeyError as e: 80 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 81 | return HttpResponse(status=500) 82 | else: 83 | try: 84 | globalpermissions.objects.filter(authorization='global').update(inception=inception, ldap=ldap, 85 | message=message, other=other) 86 | return Response('配置信息保存成功!') 87 | except Exception as e: 88 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 89 | return Response('配置信息保存失败!请通过错误日志查看具体信息') 90 | -------------------------------------------------------------------------------- /src/core/api/sqlorder.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | import sqlparse 4 | from libs import baseview, util 5 | from libs import call_inception 6 | from core.task import submit_push_messages 7 | from rest_framework.response import Response 8 | from django.http import HttpResponse 9 | from core.models import ( 10 | DatabaseList, 11 | SqlOrder 12 | ) 13 | 14 | CUSTOM_ERROR = logging.getLogger('Yearning.core.views') 15 | 16 | conf = util.conf_path() 17 | addr_ip = conf.ipaddress 18 | 19 | 20 | class sqlorder(baseview.BaseView): 21 | ''' 22 | 23 | :argument 手动模式工单提交相关接口api 24 | 25 | put 美化sql 测试sql 26 | 27 | post 提交工单 28 | 29 | ''' 30 | 31 | def put(self, request, args=None): 32 | if args == 'beautify': 33 | try: 34 | data = request.data['data'] 35 | except KeyError as e: 36 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 37 | else: 38 | try: 39 | res = call_inception.Inception.BeautifySQL(sql=data) 40 | return HttpResponse(res) 41 | except Exception as e: 42 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 43 | return HttpResponse(status=500) 44 | 45 | elif args == 'test': 46 | try: 47 | id = request.data['id'] 48 | base = request.data['base'] 49 | sql = request.data['sql'] 50 | data = DatabaseList.objects.filter(id=id).first() 51 | info = { 52 | 'host': data.ip, 53 | 'user': data.username, 54 | 'password': data.password, 55 | 'db': base, 56 | 'port': data.port 57 | } 58 | except KeyError as e: 59 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 60 | else: 61 | try: 62 | with call_inception.Inception(LoginDic=info) as test: 63 | res = test.Check(sql=sql) 64 | return Response({'result': res, 'status': 200}) 65 | except Exception as e: 66 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 67 | return HttpResponse(e) 68 | 69 | def post(self, request, args=None): 70 | try: 71 | data = json.loads(request.data['data']) 72 | tmp = json.loads(request.data['sql']) 73 | type = request.data['type'] 74 | real_name = request.data['real_name'] 75 | id = request.data['id'] 76 | except KeyError as e: 77 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 78 | return HttpResponse(status=500) 79 | else: 80 | try: 81 | x = [x for x in sqlparse.split(tmp)] 82 | if str(x[0]).lstrip().startswith('use'): 83 | del x[0] 84 | sql = ''.join(x) 85 | sql = sql.strip(' ').rstrip(';') 86 | workId = util.workId() 87 | SqlOrder.objects.get_or_create( 88 | username=request.user, 89 | date=util.date(), 90 | work_id=workId, 91 | status=2, 92 | basename=data['basename'], 93 | sql=sql, 94 | type=type, 95 | text=data['text'], 96 | backup=data['backup'], 97 | bundle_id=id, 98 | assigned=data['assigned'], 99 | delay=data['delay'], 100 | real_name=real_name 101 | ) 102 | submit_push_messages( 103 | workId=workId, 104 | user=request.user, 105 | addr_ip=addr_ip, 106 | text=data['text'], 107 | assigned=data['assigned'], 108 | id=id 109 | ).start() 110 | return Response('已提交,请等待管理员审核!') 111 | except Exception as e: 112 | CUSTOM_ERROR.error(f'{e.__class__.__name__}: {e}') 113 | return HttpResponse(status=500) 114 | -------------------------------------------------------------------------------- /src/core/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class CoreConfig(AppConfig): 5 | name = 'core' 6 | -------------------------------------------------------------------------------- /src/core/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/src/core/migrations/__init__.py -------------------------------------------------------------------------------- /src/core/models.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Create your models here. 3 | 4 | ''' 5 | from django.db import models 6 | from django.contrib.auth.models import AbstractUser 7 | import ast 8 | 9 | 10 | class JSONField(models.TextField): 11 | description = "Json" 12 | 13 | def __init__(self, *args, **kwargs): 14 | super().__init__(*args, **kwargs) 15 | 16 | def from_db_value(self, value, expression, connection, context): 17 | if not value: 18 | value = {} 19 | return ast.literal_eval(value) 20 | 21 | def get_prep_value(self, value): 22 | if value is None: 23 | return value 24 | return str(value) 25 | 26 | def value_to_string(self, obj): 27 | value = self._get_val_from_obj(obj) 28 | return self.get_db_prep_save(value) 29 | 30 | 31 | class Account(AbstractUser): 32 | ''' 33 | User table 34 | ''' 35 | group = models.CharField(max_length=40) # 权限组 guest/admin 36 | department = models.CharField(max_length=40) # 部门 37 | auth_group = models.CharField(max_length=100, null=True) # 细粒化权限组 38 | real_name = models.CharField(max_length=100, null=True, default='请添加真实姓名') # 真实姓名 39 | 40 | 41 | class SqlOrder(models.Model): 42 | ''' 43 | 工单提交表 44 | ''' 45 | work_id = models.CharField(max_length=50, blank=True) # 工单id 46 | username = models.CharField(max_length=50, blank=True) # 提交人 47 | status = models.IntegerField(blank=True) # 工单状态 0 disagree 1 agree 2 indeterminate 3 ongoing 4 faild 48 | type = models.SmallIntegerField(blank=True) # 工单类型 0 DDL 1 DML 49 | backup = models.SmallIntegerField(blank=True) # 工单是否备份 0 not backup 1 backup 50 | bundle_id = models.IntegerField(db_index=True, null=True) # Matching with Database_list id Field 51 | date = models.CharField(max_length=100, blank=True) # 提交日期 52 | basename = models.CharField(max_length=50, blank=True) # 数据库名 53 | sql = models.TextField(blank=True) # sql语句 54 | text = models.TextField(blank=True) # 工单备注 55 | assigned = models.CharField(max_length=50, blank=True) # 工单审核人 56 | delay = models.CharField(max_length=100, null=True, default='None') # 延迟时间 57 | rejected = models.TextField(blank=True) # 驳回说明 58 | real_name = models.CharField(max_length=100, null=True) # 姓名 59 | executor = models.CharField(max_length=50, null=True) # 多级审核下的执行人 60 | 61 | 62 | class DatabaseList(models.Model): 63 | ''' 64 | 数据库连接信息表 65 | ''' 66 | connection_name = models.CharField(max_length=50) # 连接名 67 | computer_room = models.CharField(max_length=50) # 机房 68 | ip = models.CharField(max_length=100) # ip地址 69 | username = models.CharField(max_length=150) # 数据库用户名 70 | port = models.IntegerField() # 端口 71 | password = models.CharField(max_length=50) # 数据库密码 72 | before = models.TextField(null=True) # 提交工单 钉钉webhook发送内容 73 | after = models.TextField(null=True) # 工单执行成功后 钉钉webhook发送内容 74 | 75 | 76 | class SqlRecord(models.Model): 77 | ''' 78 | 工单执行记录表 79 | ''' 80 | state = models.CharField(max_length=100) # 执行状态 81 | sql = models.TextField(blank=True) # 82 | error = models.TextField(null=True) 83 | workid = models.CharField(max_length=50, null=True) 84 | affectrow = models.CharField(max_length=100, null=True) 85 | sequence = models.CharField(max_length=50, null=True) 86 | execute_time = models.CharField(max_length=150, null=True) 87 | backup_dbname = models.CharField(max_length=100, null=True) 88 | SQLSHA1 = models.TextField(null=True) 89 | 90 | 91 | class Todolist(models.Model): 92 | ''' 93 | todo info 94 | ''' 95 | username = models.CharField(max_length=50) # 账户 96 | content = models.CharField(max_length=200) # 内容 97 | 98 | 99 | class globalpermissions(models.Model): 100 | ''' 101 | 102 | globalpermissions 103 | 104 | ''' 105 | authorization = models.CharField(max_length=50, null=True, db_index=True) 106 | inception = JSONField(null=True) 107 | ldap = JSONField(null=True) 108 | message = JSONField(null=True) 109 | other = JSONField(null=True) 110 | 111 | 112 | class grained(models.Model): 113 | username = models.CharField(max_length=50, db_index=True) 114 | permissions = JSONField() 115 | 116 | 117 | class applygrained(models.Model): 118 | username = models.CharField(max_length=50, db_index=True) 119 | work_id = models.CharField(max_length=50, null=True) 120 | status = models.IntegerField(blank=True, null=True) # 工单状态 0 disagree 1 agree 2 indeterminate 121 | permissions = JSONField() 122 | auth_group = models.CharField(max_length=50, null=True) 123 | real_name = models.CharField(max_length=100, null=True) # 真实姓名 124 | 125 | 126 | class querypermissions(models.Model): 127 | work_id = models.CharField(max_length=50, null=True, db_index=True) 128 | username = models.CharField(max_length=100, null=True) 129 | statements = models.TextField() 130 | 131 | 132 | class query_order(models.Model): 133 | work_id = models.CharField(max_length=50, null=True, db_index=True) 134 | username = models.CharField(max_length=100, null=True) 135 | date = models.CharField(max_length=50) 136 | instructions = models.TextField(null=True) 137 | query_per = models.SmallIntegerField(null=True, default=0) # 0拒绝 1同意 2待审核 3完毕 138 | connection_name = models.CharField(max_length=50, null=True) # 连接名 139 | computer_room = models.CharField(max_length=50, null=True) # 机房 140 | export = models.SmallIntegerField(null=True, default=0) 141 | audit = models.CharField(max_length=100, null=True) 142 | time = models.CharField(max_length=100, null=True) 143 | real_name = models.CharField(max_length=100, null=True) # 真实姓名 144 | -------------------------------------------------------------------------------- /src/deploy.conf.template: -------------------------------------------------------------------------------- 1 | [mysql] 2 | db = Yearning 3 | address = 127.0.0.1 4 | port = 3306 5 | username = root 6 | password = 7 | 8 | [host] 9 | ipaddress = 127.0.0.1:8080 10 | -------------------------------------------------------------------------------- /src/exportData/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep 4 | -------------------------------------------------------------------------------- /src/gunicorn.conf: -------------------------------------------------------------------------------- 1 | bind = '0.0.0.0:8000' 2 | workers = 2 3 | backlog = 1024 4 | worker_class = 'gevent' 5 | daemon = True 6 | proc_name = 'yearning_prc' 7 | pidfile = './yearning.pid' 8 | accesslog = './log/all.log' 9 | errorlog = './log/error.log' 10 | -------------------------------------------------------------------------------- /src/libs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/src/libs/__init__.py -------------------------------------------------------------------------------- /src/libs/baseview.py: -------------------------------------------------------------------------------- 1 | from rest_framework.permissions import ( 2 | IsAdminUser, 3 | IsAuthenticated 4 | ) 5 | from rest_framework.views import APIView 6 | 7 | 8 | class BaseView(APIView): 9 | permission_classes = (IsAuthenticated,) 10 | 11 | def get(self, request, args: str = None): 12 | pass 13 | 14 | def post(self, request, args: str = None): 15 | pass 16 | 17 | def put(self, request, args: str = None): 18 | pass 19 | 20 | def delete(self, request, args: str = None): 21 | pass 22 | 23 | 24 | class SuperUserpermissions(APIView): 25 | permission_classes = (IsAdminUser,) 26 | 27 | def get(self, request, args: str = None): 28 | pass 29 | 30 | def post(self, request, args: str = None): 31 | pass 32 | 33 | def put(self, request, args: str = None): 34 | pass 35 | 36 | def delete(self, request, args: str = None): 37 | pass 38 | 39 | 40 | class AnyLogin(APIView): 41 | permission_classes = () 42 | authentication_classes = () 43 | 44 | def get(self, request, args: str = None): 45 | pass 46 | 47 | def post(self, request, args: str = None): 48 | pass 49 | 50 | def put(self, request, args: str = None): 51 | pass 52 | 53 | def delete(self, request, args: str = None): 54 | pass 55 | -------------------------------------------------------------------------------- /src/libs/call_inception.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | INCEPTION operation 4 | 5 | 2017-11-23 6 | 7 | cookie 8 | 9 | ''' 10 | 11 | from libs import util 12 | from settingConf import settings 13 | from libs.cryptoAES import cryptoAES 14 | import pymysql 15 | import sqlparse 16 | import ast 17 | 18 | pymysql.install_as_MySQLdb() 19 | 20 | 21 | class Inception(object): 22 | def __init__(self, LoginDic=None): 23 | self.__dict__.update(LoginDic) 24 | self.AES = cryptoAES(settings.SECRET_KEY) 25 | self.con = object 26 | try: 27 | self.password = self.AES.decrypt(self.__dict__.get('password')) 28 | except ValueError: 29 | self.password = self.__dict__.get('password') 30 | 31 | def __enter__(self): 32 | un_init = util.init_conf() 33 | inception = ast.literal_eval(un_init['inception']) 34 | self.con = pymysql.connect(host=inception['host'], 35 | user=inception['user'], 36 | passwd=inception['password'], 37 | port=int(inception['port']), 38 | db='', 39 | charset="utf8") 40 | return self 41 | 42 | def GenerateStatements(self, Sql: str = '', Type: str = '', backup=None): 43 | if Sql[-1] == ';': 44 | Sql = Sql.rstrip(';') 45 | elif Sql[-1] == ';': 46 | Sql = Sql.rstrip(';') 47 | if backup is not None and backup != 0: 48 | InceptionSQL = ''' 49 | /*--user=%s;--password=%s;--host=%s;--port=%s;%s;%s;*/ \ 50 | inception_magic_start;\ 51 | use `%s`;\ 52 | %s; \ 53 | inception_magic_commit; 54 | ''' % (self.__dict__.get('user'), 55 | self.password, 56 | self.__dict__.get('host'), 57 | self.__dict__.get('port'), 58 | Type, 59 | backup, 60 | self.__dict__.get('db'), 61 | Sql) 62 | return InceptionSQL 63 | else: 64 | InceptionSQL = ''' 65 | /*--user=%s;--password=%s;--host=%s;--port=%s;%s;*/ \ 66 | inception_magic_start;\ 67 | use `%s`;\ 68 | %s; \ 69 | inception_magic_commit; 70 | ''' % (self.__dict__.get('user'), 71 | self.password, 72 | self.__dict__.get('host'), 73 | self.__dict__.get('port'), 74 | Type, 75 | self.__dict__.get('db'), 76 | Sql) 77 | return InceptionSQL 78 | 79 | def Execute(self, sql, backup: int): 80 | if backup == 1: 81 | Inceptionsql = self.GenerateStatements(Sql=sql, Type='--enable-execute') 82 | else: 83 | Inceptionsql = self.GenerateStatements( 84 | Sql=sql, 85 | Type='--enable-execute', 86 | backup='--disable-remote-backup') 87 | with self.con.cursor() as cursor: 88 | cursor.execute(Inceptionsql) 89 | result = cursor.fetchall() 90 | Dataset = [ 91 | { 92 | 'ID': row[0], 93 | 'stage': row[1], 94 | 'errlevel': row[2], 95 | 'stagestatus': row[3], 96 | 'errormessage': row[4], 97 | 'sql': row[5], 98 | 'affected_rows': row[6], 99 | 'sequence': row[7], 100 | 'backup_dbname': row[8], 101 | 'execute_time': row[9], 102 | 'SQLSHA1': row[10] 103 | } 104 | for row in result 105 | ] 106 | return Dataset 107 | 108 | def Check(self, sql=None): 109 | Inceptionsql = self.GenerateStatements(Sql=sql, Type='--enable-check') 110 | with self.con.cursor() as cursor: 111 | cursor.execute(Inceptionsql) 112 | result = cursor.fetchall() 113 | Dataset = [ 114 | { 115 | 'ID': row[0], 116 | 'stage': row[1], 117 | 'errlevel': row[2], 118 | 'stagestatus': row[3], 119 | 'errormessage': row[4], 120 | 'sql': row[5], 121 | 'affected_rows': row[6], 122 | 'SQLSHA1': row[10] 123 | } 124 | for row in result 125 | ] 126 | return Dataset 127 | 128 | def oscstep(self, sql=None): 129 | with self.con.cursor(cursor=pymysql.cursors.DictCursor) as cursor: 130 | cursor.execute(sql) 131 | result = cursor.fetchall() 132 | cursor.close() 133 | return result 134 | 135 | def __exit__(self, exc_type, exc_val, exc_tb): 136 | self.con.close() 137 | 138 | @staticmethod 139 | def BeautifySQL(sql): 140 | return sqlparse.format(sql, reindent=True, keyword_case='upper') 141 | 142 | def __str__(self): 143 | return ''' 144 | 145 | InceptionSQL Class 146 | 147 | ''' 148 | -------------------------------------------------------------------------------- /src/libs/con_database.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | About connection Database 4 | 5 | 2017-11-23 6 | 7 | cookie 8 | ''' 9 | 10 | from libs.cryptoAES import cryptoAES 11 | from settingConf import settings 12 | import pymysql 13 | 14 | 15 | class SQLgo(object): 16 | def __init__(self, ip=None, user=None, password=None, db=None, port=None): 17 | self.AES = cryptoAES(settings.SECRET_KEY) 18 | self.ip = ip 19 | self.user = user 20 | self.db = db 21 | self.port = int(port) 22 | self.con = object 23 | try: 24 | self.password = self.AES.decrypt(password) 25 | except ValueError: 26 | self.password = password 27 | 28 | @staticmethod 29 | def addDic(theIndex, word, value): 30 | theIndex.setdefault(word, []).append(value) 31 | 32 | def __enter__(self): 33 | self.con = pymysql.connect( 34 | host=self.ip, 35 | user=self.user, 36 | passwd=self.password, 37 | db=self.db, 38 | charset='utf8mb4', 39 | port=self.port 40 | ) 41 | return self 42 | 43 | def __exit__(self, exc_type, exc_val, exc_tb): 44 | self.con.close() 45 | 46 | def search(self, sql=None): 47 | data_dict = [] 48 | with self.con.cursor(cursor=pymysql.cursors.DictCursor) as cursor: 49 | sqllist = sql 50 | cursor.execute(sqllist) 51 | result = cursor.fetchall() 52 | for field in cursor.description: 53 | data_dict.append( 54 | {'title': field[0], "key": field[0], "width": 200}) 55 | len = cursor.rowcount 56 | return {'data': result, 'title': data_dict, 'len': len} 57 | 58 | def showtable(self, table_name): 59 | with self.con.cursor() as cursor: 60 | sqllist = ''' 61 | select aa.COLUMN_NAME, 62 | aa.DATA_TYPE,aa.COLUMN_COMMENT, cc.TABLE_COMMENT 63 | from information_schema.`COLUMNS` aa LEFT JOIN 64 | (select DISTINCT bb.TABLE_SCHEMA,bb.TABLE_NAME,bb.TABLE_COMMENT 65 | from information_schema.`TABLES` bb ) cc 66 | ON (aa.TABLE_SCHEMA=cc.TABLE_SCHEMA and aa.TABLE_NAME = cc.TABLE_NAME ) 67 | where aa.TABLE_SCHEMA = '%s' and aa.TABLE_NAME = '%s'; 68 | ''' % (self.db, table_name) 69 | cursor.execute(sqllist) 70 | result = cursor.fetchall() 71 | td = [ 72 | { 73 | 'Field': i[0], 74 | 'Type': i[1], 75 | 'Extra': i[2], 76 | 'TableComment': i[3] 77 | } for i in result 78 | ] 79 | return td 80 | 81 | def gen_alter(self, table_name): 82 | with self.con.cursor() as cursor: 83 | sqllist = 'desc %s.%s;' % (self.db, table_name) 84 | cursor.execute(sqllist) 85 | result = cursor.fetchall() 86 | td = [ 87 | { 88 | 'Field': i[0], 89 | 'Type': i[1], 90 | 'Null': i[2], 91 | 'Key': i[3], 92 | 'Default': i[4] 93 | } for i in result 94 | ] 95 | sqllist = 'show table status where NAME="%s";' % (table_name) 96 | cursor.execute(sqllist) 97 | result = cursor.fetchall() 98 | tablecomment = result[0][-1] 99 | [item.update(TableComment=tablecomment) for item in td] 100 | sqllist = 'show full columns from %s;' % (table_name) 101 | cursor.execute(sqllist) 102 | result = cursor.fetchall() 103 | for item in td: 104 | for item1 in result: 105 | if item['Field'] == item1[0]: 106 | item['Extra'] = item1[-1] 107 | break 108 | return td 109 | 110 | def index(self, table_name): 111 | with self.con.cursor() as cursor: 112 | cursor.execute('show keys from %s' % table_name) 113 | result = cursor.fetchall() 114 | di = [ 115 | { 116 | 'Non_unique': '是', 117 | 'key_name': i[2], 118 | 'column_name': i[4], 119 | 'index_type': i[10] 120 | } 121 | if i[1] == 0 122 | else 123 | { 124 | 'Non_unique': '否', 125 | 'key_name': i[2], 126 | 'column_name': i[4], 127 | 'index_type': i[10] 128 | } 129 | for i in result 130 | ] 131 | 132 | dic = {} 133 | c = [] 134 | for i in di: 135 | self.addDic(dic, i['key_name'], i['column_name']) 136 | for t in dic: 137 | str1 = dic[t][0] 138 | 139 | for i in range(1, len(dic[t])): 140 | str1 = str1 + ',' + dic[t][i] 141 | 142 | temp = {} 143 | for g in di: 144 | if t == g['key_name']: 145 | temp.setdefault('Non_unique', g['Non_unique']) 146 | temp.setdefault('index_type', g['index_type']) 147 | temp.setdefault('column_name', str1) 148 | temp.setdefault('key_name', t) 149 | c.append(temp) 150 | return c 151 | 152 | def baseItems(self, sql=None): 153 | 154 | with self.con.cursor() as cursor: 155 | cursor.execute(sql) 156 | result = cursor.fetchall() 157 | data = [c for i in result for c in i] 158 | return data 159 | 160 | def query_info(self, sql=None): 161 | with self.con.cursor(cursor=pymysql.cursors.DictCursor) as cursor: 162 | cursor.execute(sql) 163 | result = cursor.fetchall() 164 | return result 165 | -------------------------------------------------------------------------------- /src/libs/cryptoAES.py: -------------------------------------------------------------------------------- 1 | from Crypto.Cipher import AES 2 | from binascii import b2a_hex, a2b_hex 3 | 4 | 5 | class cryptoAES(object): 6 | def __init__(self, key): 7 | self.key = key[0:16] 8 | self.mode = AES.MODE_CBC 9 | self.ciphertext = None 10 | 11 | def encrypt(self, text): 12 | cryptor = AES.new(self.key, self.mode, self.key) 13 | length = 16 14 | count = len(text) 15 | if count % length != 0: 16 | add = length - (count % length) 17 | else: 18 | add = 0 19 | text = text + ('\0' * add) 20 | self.ciphertext = cryptor.encrypt(text) 21 | return bytes.decode(b2a_hex(self.ciphertext)) 22 | 23 | def decrypt(self, text): 24 | cryptor = AES.new(self.key, self.mode, self.key) 25 | plain_text = cryptor.decrypt(a2b_hex(text)) 26 | return bytes.decode(plain_text.rstrip(b'\0')) 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/libs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/src/libs/logo.png -------------------------------------------------------------------------------- /src/libs/rollback.py: -------------------------------------------------------------------------------- 1 | from libs import con_database, util 2 | import ast 3 | 4 | 5 | def rollbackSQL(db=None, opid=None): 6 | un_init = util.init_conf() 7 | inception = ast.literal_eval(un_init['inception']) 8 | with con_database.SQLgo( 9 | ip=inception["back_host"], 10 | user=inception["back_user"], 11 | password=inception["back_password"], 12 | db=db, 13 | port=inception["back_port"] 14 | ) as f: 15 | data = f.query_info( 16 | sql= 17 | ''' 18 | select tablename from $_$Inception_backup_information$_$ where opid_time =%s; 19 | ''' % opid) 20 | return data[0] 21 | 22 | 23 | def roll(backdb=None, opid=None): 24 | un_init = util.init_conf() 25 | inception = ast.literal_eval(un_init['inception']) 26 | with con_database.SQLgo( 27 | ip=inception["back_host"], 28 | user=inception["back_user"], 29 | password=inception["back_password"], 30 | port=inception["back_port"] 31 | ) as f: 32 | data = f.query_info( 33 | sql= 34 | ''' 35 | select rollback_statement from %s where opid_time =%s; 36 | ''' % (backdb, opid)) 37 | return data 38 | -------------------------------------------------------------------------------- /src/libs/serializers.py: -------------------------------------------------------------------------------- 1 | ''' 2 | serializers 3 | ''' 4 | from rest_framework import serializers 5 | from core.models import DatabaseList 6 | from core.models import SqlRecord 7 | from core.models import Account 8 | from core.models import SqlOrder, query_order, querypermissions, globalpermissions, grained 9 | 10 | 11 | class Globalpermissions(serializers.HyperlinkedModelSerializer): 12 | ''' 13 | 站内信列表serializers 14 | ''' 15 | 16 | class Meta: 17 | model = globalpermissions 18 | fields = ('inception', 'ldap', 'other', 'message') 19 | 20 | 21 | class UserINFO(serializers.HyperlinkedModelSerializer): 22 | ''' 23 | 平台用户信息列表serializers 24 | ''' 25 | 26 | class Meta: 27 | model = Account 28 | fields = ('id', 'username', 'group', 'department', 'email', 'auth_group', 'real_name') 29 | 30 | 31 | class Sqllist(serializers.HyperlinkedModelSerializer): 32 | ''' 33 | 数据库连接信息serializers 34 | ''' 35 | 36 | class Meta: 37 | model = DatabaseList 38 | fields = ('id', 'connection_name', 'ip', 'computer_room', 'password', 'port', 'username', 'before', 'after') 39 | 40 | 41 | class query_con(serializers.HyperlinkedModelSerializer): 42 | ''' 43 | 查询连接信息serializers 44 | ''' 45 | 46 | class Meta: 47 | model = DatabaseList 48 | fields = ('connection_name', 'computer_room') 49 | 50 | 51 | class Area(serializers.HyperlinkedModelSerializer): 52 | ''' 53 | SQL提交及表结构修改页面数据库连接信息返回serializers 54 | ''' 55 | 56 | class Meta: 57 | model = DatabaseList 58 | fields = ('id', 'connection_name', 'ip', 'computer_room') 59 | 60 | 61 | class Record(serializers.HyperlinkedModelSerializer): 62 | ''' 63 | 执行工单的详细信息serializers 64 | ''' 65 | 66 | class Meta: 67 | model = SqlRecord 68 | fields = ('sql', 'state', 'error', 'affectrow', 'sequence', 'execute_time') 69 | 70 | 71 | class Recordinfo(serializers.HyperlinkedModelSerializer): 72 | ''' 73 | 74 | 执行记录 返回 75 | 76 | ''' 77 | 78 | class Meta: 79 | model = SqlOrder 80 | fields = ('workid', 'username', 'text', 'data', 'basename', 'assigned') 81 | 82 | 83 | class Query_review(serializers.HyperlinkedModelSerializer): 84 | ''' 85 | 86 | 查询审计 87 | 88 | ''' 89 | 90 | class Meta: 91 | model = query_order 92 | fields = ( 93 | 'work_id', 'username', 'date', 'instructions', 'query_per', 'connection_name', 'computer_room', 94 | 'export', 'time', 'real_name') 95 | 96 | 97 | class Query_list(serializers.HyperlinkedModelSerializer): 98 | ''' 99 | 100 | 查询审计 101 | 102 | ''' 103 | 104 | class Meta: 105 | model = querypermissions 106 | fields = ('id', 'statements') 107 | 108 | 109 | class AuthGroup_Serializers(serializers.HyperlinkedModelSerializer): 110 | """ 111 | 序列化权限组 112 | """ 113 | 114 | class Meta: 115 | model = grained 116 | fields = ('id', 'username', 'permissions',) 117 | -------------------------------------------------------------------------------- /src/libs/util.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | Some tool sets 4 | 5 | 2017-11-23 6 | 7 | cookie 8 | 9 | ''' 10 | 11 | import json 12 | import random 13 | import ssl 14 | import time 15 | import configparser 16 | import ast 17 | from urllib import request 18 | from collections import namedtuple 19 | from ldap3 import Server, Connection, SUBTREE, ALL 20 | from libs import con_database 21 | 22 | _conf = configparser.ConfigParser() 23 | _conf.read('deploy.conf') 24 | 25 | 26 | def dingding(content: str = None, url: str = None): 27 | ''' 28 | 29 | dingding webhook 30 | 31 | ''' 32 | 33 | pdata = {"msgtype": "markdown", "markdown": {"title": "Yearning sql审计平台", "text": content}} 34 | binary_data = json.dumps(pdata).encode(encoding='UTF8') 35 | headers = {"Content-Type": "application/json"} 36 | req = request.Request(url, headers=headers) 37 | context = ssl._create_unverified_context() 38 | request.urlopen(req, data=binary_data, context=context).read() 39 | 40 | 41 | def date() -> str: 42 | ''' 43 | datetime 44 | ''' 45 | now = time.strftime('%Y-%m-%d %H:%M', time.localtime(time.time())) 46 | return now 47 | 48 | 49 | def workId() -> str: 50 | ''' 51 | 工单 52 | ''' 53 | now = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time())) 54 | _ran = ''.join(random.sample('0123456789', 4)) 55 | 56 | now = f'{now}{_ran}' 57 | return now 58 | 59 | 60 | def ser(_obj: object) -> list: 61 | ''' 62 | orm.raw 序列化 63 | ''' 64 | _list = [] 65 | _get = [] 66 | for i in _obj: 67 | _list.append(i.__dict__) 68 | 69 | for i in _list: 70 | del i['_state'] 71 | _get.append(i) 72 | return _get 73 | 74 | 75 | def conf_path() -> object: 76 | ''' 77 | 读取配置文件属性 78 | ''' 79 | conf_set = namedtuple( 80 | "name", ["db", "address", "port", "username", "password", "ipaddress"]) 81 | 82 | return conf_set(_conf.get('mysql', 'db'), _conf.get('mysql', 'address'), 83 | _conf.get('mysql', 'port'), _conf.get('mysql', 'username'), 84 | _conf.get('mysql', 'password'), _conf.get('host', 'ipaddress')) 85 | 86 | 87 | class LDAPConnection(object): 88 | def __init__(self, url, user, password): 89 | server = Server(url, get_info=ALL) 90 | self.conn = Connection(server, user=user, password=password, check_names=True, lazy=False, 91 | raise_exceptions=False) 92 | 93 | def __enter__(self): 94 | self.conn.bind() 95 | return self.conn 96 | 97 | def __exit__(self, exc_type, exc_val, exc_tb): 98 | self.conn.unbind() 99 | 100 | 101 | def test_auth(url, user, password): 102 | with LDAPConnection(url, user, password) as conn: 103 | if conn.bind(): 104 | return True 105 | return False 106 | 107 | 108 | def auth(username, password): 109 | un_init = init_conf() 110 | ldap = ast.literal_eval(un_init['ldap']) 111 | 112 | LDAP_TYPE = ldap['type'] 113 | LDAP_SCBASE = ldap['sc'] 114 | 115 | if LDAP_TYPE == '1': 116 | search_filter = '(sAMAccountName={})'.format(username) 117 | elif LDAP_TYPE == '2': 118 | search_filter = '(uid={})'.format(username) 119 | else: 120 | search_filter = '(cn={})'.format(username) 121 | 122 | with LDAPConnection(ldap['url'], ldap['user'], ldap['password']) as conn: 123 | 124 | res = conn.search( 125 | search_base=LDAP_SCBASE, 126 | search_filter=search_filter, 127 | search_scope=SUBTREE, 128 | attributes=['cn', 'uid', 'mail'], 129 | ) 130 | if res: 131 | entry = conn.response[0] 132 | # check password by dn 133 | try: 134 | if conn.rebind(user=entry['dn'], password=password): 135 | attr_dict = entry['attributes'] 136 | print((True, attr_dict["mail"], attr_dict["cn"], attr_dict["uid"])) 137 | return True 138 | except Exception as e: 139 | print(str(e)) 140 | return False 141 | 142 | 143 | def init_conf(): 144 | with con_database.SQLgo( 145 | ip=_conf.get('mysql', 'address'), 146 | user=_conf.get('mysql', 'username'), 147 | password=_conf.get('mysql', 'password'), 148 | db=_conf.get('mysql', 'db'), 149 | port=_conf.get('mysql', 'port')) as f: 150 | res = f.query_info( 151 | "select * from core_globalpermissions where authorization = 'global'") 152 | 153 | return res[0] 154 | -------------------------------------------------------------------------------- /src/log/.gitkeep: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file !.gitkeep 4 | -------------------------------------------------------------------------------- /src/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", "settingConf.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /src/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.2.20 2 | django-cors-headers==2.1.0 3 | djangorestframework==3.11.2 4 | djangorestframework-jwt==1.11.0 5 | PyMySQL==0.9.2 6 | sqlparse==0.2.4 7 | ldap3==2.4.1 8 | simplejson==3.15.0 9 | gunicorn==19.8.1 10 | -------------------------------------------------------------------------------- /src/settingConf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/src/settingConf/__init__.py -------------------------------------------------------------------------------- /src/settingConf/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for auto_deploy project. 3 | 4 | Generated by 'django-admin startproject' using Django 1.10.6. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/1.10/ref/settings/ 11 | """ 12 | import os 13 | import datetime 14 | from libs import util 15 | 16 | CONF_DATA = util.conf_path() 17 | 18 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 19 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 20 | 21 | # Quick-start development settings - unsuitable for production 22 | # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/ 23 | 24 | # SECURITY WARNING: keep the secret key used in production secret! 25 | SECRET_KEY = 'u)zall!ag&mci+ja5u&-6*1e^ufyu)l4i8+^=mw$845@k!ie+3.txt' 26 | 27 | # SECURITY WARNING: don't run with debug turned on in production! 28 | DEBUG = False 29 | 30 | # ALLOWED_HOSTS = [str(CONF_DATA.ipaddress).split(':')[0]] 31 | ALLOWED_HOSTS = ['*'] 32 | 33 | # Application definition 34 | AUTH_USER_MODEL = 'core.Account' 35 | 36 | INSTALLED_APPS = [ 37 | 'django.contrib.auth', 38 | 'django.contrib.contenttypes', 39 | 'core.apps.CoreConfig', 40 | 'rest_framework', 41 | 'django.contrib.staticfiles', 42 | 'settingConf' 43 | ] 44 | 45 | MIDDLEWARE = [ 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 49 | 'corsheaders.middleware.CorsMiddleware', 50 | 'django.middleware.common.CommonMiddleware' 51 | ] 52 | CORS_ORIGIN_ALLOW_ALL = True 53 | CSRF_ORIGIN_ALLOW_ALL = True 54 | CORS_ALLOW_METHODS = ( 55 | 'DELETE', 56 | 'GET', 57 | 'OPTIONS', 58 | 'POST', 59 | 'PUT', 60 | ) 61 | 62 | ROOT_URLCONF = 'settingConf.urls' 63 | 64 | WSGI_APPLICATION = 'settingConf.wsgi.application' 65 | 66 | # Database 67 | # https://docs.djangoproject.com/en/1.10/ref/settings/#databases 68 | 69 | DATABASES = { 70 | 'default': { 71 | 'ENGINE': 'django.db.backends.mysql', 72 | 'NAME': CONF_DATA.db, 73 | 'USER': CONF_DATA.username, 74 | "PORT": CONF_DATA.port, 75 | "PASSWORD": CONF_DATA.password, 76 | "HOST": CONF_DATA.address, 77 | } 78 | } 79 | 80 | TEMPLATES = [ 81 | { 82 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 83 | 'DIRS': ['dist'], 84 | 'APP_DIRS': True, 85 | 'OPTIONS': { 86 | 'context_processors': [ 87 | 'django.template.context_processors.debug', 88 | 'django.template.context_processors.request', 89 | 'django.contrib.auth.context_processors.auth', 90 | 'django.contrib.messages.context_processors.messages', 91 | ], 92 | }, 93 | }, 94 | ] 95 | 96 | STATIC_URL = '/static/' 97 | 98 | STATICFILES_DIRS = ( 99 | os.path.join(BASE_DIR, 'dist/static').replace('\\', '/'), 100 | ) 101 | 102 | # Password validation 103 | # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators 104 | 105 | AUTH_PASSWORD_VALIDATORS = [ 106 | { 107 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 108 | }, 109 | { 110 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 111 | }, 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 114 | }, 115 | { 116 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 117 | }, 118 | ] 119 | 120 | # Internationalization 121 | # https://docs.djangoproject.com/en/1.10/topics/i18n/ 122 | 123 | LANGUAGE_CODE = 'en-us' 124 | 125 | TIME_ZONE = 'Asia/Shanghai' 126 | 127 | USE_I18N = True 128 | 129 | USE_L10N = True 130 | 131 | USE_TZ = True 132 | 133 | REST_FRAMEWORK = { 134 | 'DEFAULT_PERMISSION_CLASSES': ( 135 | 'rest_framework.permissions.IsAuthenticated', 136 | ), 137 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 138 | 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', 139 | ), 140 | } 141 | JWT_AUTH = { 142 | 'JWT_RESPONSE_PAYLOAD_HANDLER': 143 | 'rest_framework_jwt.utils.jwt_response_payload_handler', 144 | 'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=7200), 145 | } 146 | 147 | LOGGING = { 148 | 'version': 1, 149 | 'disable_existing_loggers': True, 150 | 'formatters': { 151 | 'standard': { 152 | 'format': '%(asctime)s [%(threadName)s:%(thread)d] \ 153 | [%(name)s:%(lineno)d] [%(levelname)s]- %(message)s'} 154 | # 日志格式 155 | }, 156 | 'filters': { 157 | }, 158 | 'handlers': { 159 | 'error': { 160 | 'level': 'ERROR', 161 | 'class': 'logging.handlers.RotatingFileHandler', 162 | 'filename': 'log/error.log', 163 | 'maxBytes': 1024 * 1024 * 100, 164 | 'backupCount': 1, 165 | 'formatter': 'standard' 166 | }, 167 | 'default': { 168 | 'level': 'INFO', 169 | 'class': 'logging.handlers.RotatingFileHandler', 170 | 'filename': 'log/all.log', # 日志输出文件 171 | 'maxBytes': 1024 * 1024 * 100, # 文件大小 172 | 'backupCount': 1, # 备份份数 173 | 'formatter': 'standard', # 使用哪种formatters日志格式 174 | }, 175 | 'console': { 176 | 'level': 'DEBUG', 177 | 'class': 'logging.StreamHandler', 178 | 'formatter': 'standard' 179 | } 180 | }, 181 | 'loggers': { 182 | 'django': { 183 | 'handlers': ['default', 'console'], 184 | 'level': 'INFO', 185 | 'propagate': False 186 | }, 187 | 'Yearning.core.views': { 188 | 'handlers': ['error'], 189 | 'level': 'ERROR', 190 | 'propagate': True 191 | } 192 | 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/settingConf/urls.py: -------------------------------------------------------------------------------- 1 | ''' 2 | url table 3 | ''' 4 | 5 | from django.conf.urls import url 6 | from django.views.generic import TemplateView 7 | from rest_framework.urlpatterns import format_suffix_patterns 8 | 9 | from core.api.user import ( 10 | userinfo, 11 | ldapauth, 12 | login_auth, 13 | login_register 14 | ) 15 | from core.api.dashboard import ( 16 | dashboard 17 | ) 18 | from core.api.managerdb import ( 19 | management_db, 20 | dingding 21 | ) 22 | from core.api.auditorder import ( 23 | audit, 24 | del_order, 25 | getsql 26 | ) 27 | from core.api.record import ( 28 | record_order, 29 | order_detail 30 | ) 31 | from core.api.applygrained import ( 32 | audit_grained, 33 | apply_grained 34 | ) 35 | from core.api.sqlorder import sqlorder 36 | from core.api.serachsql import search, query_worklf, Query_order 37 | from core.api.osc import osc_step 38 | from core.api.myorder import order 39 | from core.api.general import addressing, exAES 40 | from core.api.setting import setting_view 41 | from core.api.authgroup import auth_group 42 | 43 | urlpatterns = [ 44 | url(r'^api/v1/exaes', exAES.as_view()), 45 | url(r'^api/v1/authgroup/(.*)', auth_group.as_view()), 46 | url(r'^api/v1/getsql', getsql.as_view()), 47 | url(r'^api/v1/setting/(.*)', setting_view.as_view()), 48 | url(r'^api/v1/query_order', Query_order.as_view()), 49 | url(r'^api/v1/query_worklf', query_worklf.as_view()), 50 | url(r'^api/v1/userinfo/(.*)', userinfo.as_view()), 51 | url(r'^api/v1/loginregister/(.*)', login_register.as_view()), 52 | url(r'^api/v1/audit_grained/(.*)', audit_grained.as_view()), 53 | url(r'^api/v1/apply_grained', apply_grained.as_view()), 54 | url(r'^api/v1/workorder/(.*)', addressing.as_view()), 55 | url(r'^api/v1/myorder', order.as_view()), 56 | url(r'^api/v1/management_db/(.*)', management_db.as_view()), 57 | url(r'^api/v1/audit_sql', audit.as_view()), 58 | url(r'^api/v1/sqlsyntax/(.*)', sqlorder.as_view()), 59 | url(r'^api/v1/record/(.*)', record_order.as_view()), 60 | url(r'^api/v1/homedata/(.*)', dashboard.as_view()), 61 | url(r'^api/v1/dingding', dingding.as_view()), 62 | url(r'^api/v1/detail', order_detail.as_view()), 63 | url(r'^api/v1/search', search.as_view()), 64 | url(r'^api/v1/ldapauth', ldapauth.as_view()), 65 | url(r'^api/v1/undoOrder', del_order.as_view()), 66 | url(r'^api/v1/osc/(.*)', osc_step.as_view()), 67 | url(r'^api-token-auth/', login_auth.as_view()), 68 | url(r'^$', TemplateView.as_view(template_name="index.html")), 69 | ] 70 | urlpatterns = format_suffix_patterns(urlpatterns) 71 | -------------------------------------------------------------------------------- /src/settingConf/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for auto_deploy 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/1.10/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", "settingConf.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /webpage/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false 5 | }], 6 | "stage-3" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /webpage/.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 | x-invalid-end-tag = false 11 | -------------------------------------------------------------------------------- /webpage/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /webpage/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html', 17 | 'vue' 18 | ], 19 | // add your custom rules here 20 | 'rules': { 21 | // allow paren-less arrow functions 22 | 'arrow-parens': 0, 23 | // allow async-await 24 | 'generator-star-spacing': 0, 25 | // allow debugger during development 26 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 27 | "indent": ["off", 4, { "SwitchCase": 1 }], 28 | "quotes": ["error", "single"], 29 | "semi": ["off", "always"], 30 | "vue/no-parsing-error": [2, { "x-invalid-end-tag": false }] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /webpage/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | test/unit/coverage 7 | test/e2e/reports 8 | selenium-debug.log 9 | .idea/* 10 | package-lock.json 11 | dist/* 12 | -------------------------------------------------------------------------------- /webpage/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /webpage/build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /webpage/build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | var shell = require('shelljs') 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | ] 16 | 17 | if (shell.which('npm')) { 18 | versionRequirements.push({ 19 | name: 'npm', 20 | currentVersion: exec('npm --version'), 21 | versionRequirement: packageConfig.engines.npm 22 | }) 23 | } 24 | 25 | module.exports = function () { 26 | var warnings = [] 27 | for (var i = 0; i < versionRequirements.length; i++) { 28 | var mod = versionRequirements[i] 29 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 30 | warnings.push(mod.name + ': ' + 31 | chalk.red(mod.currentVersion) + ' should be ' + 32 | chalk.green(mod.versionRequirement) 33 | ) 34 | } 35 | } 36 | 37 | if (warnings.length) { 38 | console.log('') 39 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 40 | console.log() 41 | for (var i = 0; i < warnings.length; i++) { 42 | var warning = warnings[i] 43 | console.log(' ' + warning) 44 | } 45 | console.log() 46 | process.exit(1) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /webpage/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /webpage/build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = process.env.NODE_ENV === 'testing' 14 | ? require('./webpack.prod.conf') 15 | : require('./webpack.dev.conf') 16 | 17 | // default port where dev server listens for incoming traffic 18 | var port = process.env.PORT || config.dev.port 19 | // automatically open browser, if not set will be false 20 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 21 | // Define HTTP proxies to your custom API backend 22 | // https://github.com/chimurai/http-proxy-middleware 23 | var proxyTable = config.dev.proxyTable 24 | 25 | var app = express() 26 | var compiler = webpack(webpackConfig) 27 | 28 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 29 | publicPath: webpackConfig.output.publicPath, 30 | quiet: true, 31 | watchOptions: { 32 | poll: true 33 | } 34 | }) 35 | 36 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 37 | log: () => {} 38 | }) 39 | // force page reload when html-webpack-plugin template changes 40 | compiler.plugin('compilation', function (compilation) { 41 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 42 | hotMiddleware.publish({ action: 'reload' }) 43 | cb() 44 | }) 45 | }) 46 | 47 | // proxy api requests 48 | Object.keys(proxyTable).forEach(function (context) { 49 | var options = proxyTable[context] 50 | if (typeof options === 'string') { 51 | options = { target: options } 52 | } 53 | app.use(proxyMiddleware(options.filter || context, options)) 54 | }) 55 | 56 | // handle fallback for HTML5 history API 57 | app.use(require('connect-history-api-fallback')()) 58 | 59 | // serve webpack bundle output 60 | app.use(devMiddleware) 61 | 62 | // enable hot-reload and state-preserving 63 | // compilation error display 64 | app.use(hotMiddleware) 65 | 66 | // serve pure static assets 67 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 68 | app.use(staticPath, express.static('./static')) 69 | 70 | var uri = 'http://127.0.0.1:' + port 71 | 72 | var _resolve 73 | var readyPromise = new Promise(resolve => { 74 | _resolve = resolve 75 | }) 76 | 77 | console.log('> Starting dev server...') 78 | devMiddleware.waitUntilValid(() => { 79 | console.log('> Listening at ' + uri + '\n') 80 | // when env is testing, don't need open it 81 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 82 | opn(uri) 83 | } 84 | _resolve() 85 | }) 86 | 87 | var server = app.listen(port) 88 | 89 | module.exports = { 90 | ready: readyPromise, 91 | close: () => { 92 | server.close() 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /webpage/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /webpage/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /webpage/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | function resolve (dir) { 6 | return path.join(__dirname, '..', dir) 7 | } 8 | 9 | module.exports = { 10 | entry: { 11 | app: './src/main.js' 12 | }, 13 | output: { 14 | path: config.build.assetsRoot, 15 | filename: '[name].js', 16 | publicPath: process.env.NODE_ENV === 'production' 17 | ? config.build.assetsPublicPath 18 | : config.dev.assetsPublicPath 19 | }, 20 | resolve: { 21 | extensions: ['.js', '.vue', '.json'], 22 | alias: { 23 | 'vue$': 'vue/dist/vue.esm.js', 24 | '@': resolve('src') 25 | } 26 | }, 27 | module: { 28 | rules: [ 29 | { 30 | test: /\.(js|vue)$/, 31 | loader: 'eslint-loader', 32 | enforce: 'pre', 33 | include: [resolve('src'), resolve('test')], 34 | options: { 35 | formatter: require('eslint-friendly-formatter') 36 | } 37 | }, 38 | { 39 | test: /\.vue$/, 40 | loader: 'vue-loader', 41 | options: vueLoaderConfig 42 | }, 43 | { 44 | test: /\.js$/, 45 | loader: 'babel-loader', 46 | include: [resolve('src'), resolve('test'),resolve('node_modules/iview')] 47 | }, 48 | { 49 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 50 | loader: 'url-loader', 51 | options: { 52 | limit: 10000, 53 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 54 | } 55 | }, 56 | { 57 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 58 | loader: 'url-loader', 59 | options: { 60 | limit: 10000, 61 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 62 | } 63 | } 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /webpage/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 12 | }) 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env 23 | }), 24 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 25 | new webpack.HotModuleReplacementPlugin(), 26 | new webpack.NoEmitOnErrorsPlugin(), 27 | // https://github.com/ampedandwired/html-webpack-plugin 28 | new HtmlWebpackPlugin({ 29 | filename: 'index.html', 30 | template: 'index.html', 31 | inject: true 32 | }), 33 | new FriendlyErrorsPlugin() 34 | ] 35 | }) 36 | -------------------------------------------------------------------------------- /webpage/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var webpack = require('webpack') 4 | var config = require('../config') 5 | var merge = require('webpack-merge') 6 | var baseWebpackConfig = require('./webpack.base.conf') 7 | var CopyWebpackPlugin = require('copy-webpack-plugin') 8 | var HtmlWebpackPlugin = require('html-webpack-plugin') 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 11 | 12 | var env = process.env.NODE_ENV === 'testing' 13 | ? require('../config/test.env') 14 | : config.build.env 15 | 16 | var webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? '#source-map' : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | new webpack.optimize.UglifyJsPlugin({ 35 | compress: { 36 | warnings: false 37 | }, 38 | sourceMap: true 39 | }), 40 | // extract css into its own file 41 | new ExtractTextPlugin({ 42 | filename: utils.assetsPath('css/[name].[contenthash].css') 43 | }), 44 | // Compress extracted CSS. We are using this plugin so that possible 45 | // duplicated CSS from different components can be deduped. 46 | new OptimizeCSSPlugin({ 47 | cssProcessorOptions: { 48 | safe: true 49 | } 50 | }), 51 | // generate dist index.html with correct asset hash for caching. 52 | // you can customize output by editing /index.html 53 | // see https://github.com/ampedandwired/html-webpack-plugin 54 | new HtmlWebpackPlugin({ 55 | filename: process.env.NODE_ENV === 'testing' 56 | ? 'index.html' 57 | : config.build.index, 58 | template: 'index.html', 59 | inject: true, 60 | minify: { 61 | removeComments: true, 62 | collapseWhitespace: true, 63 | removeAttributeQuotes: true 64 | // more options: 65 | // https://github.com/kangax/html-minifier#options-quick-reference 66 | }, 67 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 68 | chunksSortMode: 'dependency' 69 | }), 70 | // split vendor js into its own file 71 | new webpack.optimize.CommonsChunkPlugin({ 72 | name: 'vendor', 73 | minChunks: function (module, count) { 74 | // any required modules inside node_modules are extracted to vendor 75 | return ( 76 | module.resource && 77 | /\.js$/.test(module.resource) && 78 | module.resource.indexOf( 79 | path.join(__dirname, '../node_modules') 80 | ) === 0 81 | ) 82 | } 83 | }), 84 | // extract webpack runtime and module manifest to its own file in order to 85 | // prevent vendor hash from being updated whenever app bundle is updated 86 | new webpack.optimize.CommonsChunkPlugin({ 87 | name: 'manifest', 88 | chunks: ['vendor'] 89 | }), 90 | // copy custom static assets 91 | new CopyWebpackPlugin([ 92 | { 93 | from: path.resolve(__dirname, '../static'), 94 | to: config.build.assetsSubDirectory, 95 | ignore: ['.*'] 96 | } 97 | ]) 98 | ] 99 | }) 100 | 101 | if (config.build.productionGzip) { 102 | var CompressionWebpackPlugin = require('compression-webpack-plugin') 103 | 104 | webpackConfig.plugins.push( 105 | new CompressionWebpackPlugin({ 106 | asset: '[path].gz[query]', 107 | algorithm: 'gzip', 108 | test: new RegExp( 109 | '\\.(' + 110 | config.build.productionGzipExtensions.join('|') + 111 | ')$' 112 | ), 113 | threshold: 10240, 114 | minRatio: 0.8 115 | }) 116 | ) 117 | } 118 | 119 | if (config.build.bundleAnalyzerReport) { 120 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 121 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 122 | } 123 | 124 | module.exports = webpackConfig 125 | -------------------------------------------------------------------------------- /webpage/build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | // This is the webpack config used for unit tests. 2 | 3 | var utils = require('./utils') 4 | var webpack = require('webpack') 5 | var merge = require('webpack-merge') 6 | var baseConfig = require('./webpack.base.conf') 7 | 8 | var webpackConfig = merge(baseConfig, { 9 | // use inline sourcemap for karma-sourcemap-loader 10 | module: { 11 | rules: utils.styleLoaders() 12 | }, 13 | devtool: '#inline-source-map', 14 | resolveLoader: { 15 | alias: { 16 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 17 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 18 | 'scss-loader': 'sass-loader' 19 | } 20 | }, 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': require('../config/test.env') 24 | }) 25 | ] 26 | }) 27 | 28 | // no need for app entry during tests 29 | delete webpackConfig.entry 30 | 31 | module.exports = webpackConfig 32 | -------------------------------------------------------------------------------- /webpage/config/dev.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var prodEnv = require('./prod.env') 3 | 4 | module.exports = merge(prodEnv, { 5 | NODE_ENV: '"development"' 6 | }) 7 | -------------------------------------------------------------------------------- /webpage/config/env.js: -------------------------------------------------------------------------------- 1 | export default "development"; -------------------------------------------------------------------------------- /webpage/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | module.exports = { 4 | build: { 5 | env: require('./prod.env'), 6 | index: path.resolve(__dirname, '../dist/index.html'), 7 | assetsRoot: path.resolve(__dirname, '../dist'), 8 | assetsSubDirectory: 'static', 9 | assetsPublicPath: '/', 10 | productionSourceMap: true, 11 | // Gzip off by default as many popular static hosts such as 12 | // Surge or Netlify already gzip all static assets for you. 13 | // Before setting to `true`, make sure to: 14 | // npm install --save-dev compression-webpack-plugin 15 | productionGzip: false, 16 | productionGzipExtensions: ['js', 'css'], 17 | // Run the build command with an extra argument to 18 | // View the bundle analyzer report after build finishes: 19 | // `npm run build --report` 20 | // Set to `true` or `false` to always turn it on or off 21 | bundleAnalyzerReport: process.env.npm_config_report 22 | }, 23 | dev: { 24 | env: require('./dev.env'), 25 | port: 8080, 26 | autoOpenBrowser: false, 27 | assetsSubDirectory: 'static',//必须 28 | assetsPublicPath: '/', 29 | proxyTable: {}, 30 | // CSS Sourcemaps off by default because relative paths are "buggy" 31 | // with this option, according to the CSS-Loader README 32 | // (https://github.com/webpack/css-loader#sourcemaps) 33 | // In our experience, they generally work as expected, 34 | // just be aware of this issue when enabling this option. 35 | cssSourceMap: false 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /webpage/config/prod.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"production"' 3 | } 4 | -------------------------------------------------------------------------------- /webpage/config/test.env.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge') 2 | var devEnv = require('./dev.env') 3 | 4 | module.exports = merge(devEnv, { 5 | NODE_ENV: '"testing"' 6 | }) 7 | -------------------------------------------------------------------------------- /webpage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yearning 6 | 7 | 8 | 9 | 10 | 28 | 29 | 30 |
31 |
32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /webpage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deploy", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "cookie", 6 | "private": true, 7 | "scripts": { 8 | "dev": "node build/dev-server.js", 9 | "start": "node build/dev-server.js", 10 | "build": "node build/build.js", 11 | "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", 12 | "e2e": "node test/e2e/runner.js", 13 | "test": "npm run unit && npm run e2e", 14 | "lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs" 15 | }, 16 | "dependencies": { 17 | "axios": "^0.19.2", 18 | "config": "^1.27.0", 19 | "countup": "^1.8.2", 20 | "echarts": "^3.7.2", 21 | "js-cookie": "^2.2.0", 22 | "particles.js": "^2.0.0", 23 | "stylus": "^0.54.5", 24 | "stylus-loader": "^3.0.1", 25 | "vue": "^2.5.13", 26 | "vue-router": "^2.8.1", 27 | "vuex": "^2.4.0" 28 | }, 29 | "devDependencies": { 30 | "autoprefixer": "^6.7.2", 31 | "babel-core": "^6.22.1", 32 | "babel-eslint": "^7.1.1", 33 | "babel-loader": "^6.2.10", 34 | "babel-plugin-istanbul": "^4.1.1", 35 | "babel-plugin-transform-runtime": "^6.22.0", 36 | "babel-preset-env": "^1.3.2", 37 | "babel-preset-stage-0": "^6.24.1", 38 | "babel-preset-stage-2": "^6.22.0", 39 | "babel-preset-stage-3": "^6.24.1", 40 | "babel-register": "^6.22.0", 41 | "brace": "^0.11.0", 42 | "chai": "^3.5.0", 43 | "chalk": "^1.1.3", 44 | "chromedriver": "^2.27.2", 45 | "connect-history-api-fallback": "^1.3.0", 46 | "copy-webpack-plugin": "^4.0.1", 47 | "cross-env": "^4.0.0", 48 | "cross-spawn": "^5.0.1", 49 | "css-loader": "^0.28.0", 50 | "emmet": "git+https://github.com/cloud9ide/emmet-core.git", 51 | "eslint": "^4.19.1", 52 | "eslint-config-standard": "^6.2.1", 53 | "eslint-friendly-formatter": "^2.0.7", 54 | "eslint-loader": "^1.7.1", 55 | "eslint-plugin-html": "^2.0.0", 56 | "eslint-plugin-promise": "^3.4.0", 57 | "eslint-plugin-standard": "^2.0.1", 58 | "eslint-plugin-vue": "^4.2.2", 59 | "eventsource-polyfill": "^0.9.6", 60 | "express": "^4.14.1", 61 | "extract-text-webpack-plugin": "^2.0.0", 62 | "file-loader": "^0.11.1", 63 | "friendly-errors-webpack-plugin": "^1.1.3", 64 | "html-webpack-plugin": "^2.28.0", 65 | "http-proxy-middleware": "^0.17.3", 66 | "inject-loader": "^3.0.0", 67 | "iview": "^3.2.1", 68 | "karma": "^1.4.1", 69 | "karma-coverage": "^1.1.1", 70 | "karma-mocha": "^1.3.0", 71 | "karma-phantomjs-launcher": "^1.0.2", 72 | "karma-phantomjs-shim": "^1.4.0", 73 | "karma-sinon-chai": "^1.3.1", 74 | "karma-sourcemap-loader": "^0.3.7", 75 | "karma-spec-reporter": "0.0.30", 76 | "karma-webpack": "^2.0.2", 77 | "less": "^2.7.1", 78 | "less-loader": "^2.2.3", 79 | "lolex": "^1.5.2", 80 | "nightwatch": "^0.9.12", 81 | "opn": "^4.0.2", 82 | "optimize-css-assets-webpack-plugin": "^1.3.0", 83 | "ora": "^1.2.0", 84 | "phantomjs-prebuilt": "^2.1.14", 85 | "rimraf": "^2.6.0", 86 | "selenium-server": "^3.0.1", 87 | "semver": "^5.3.0", 88 | "shelljs": "^0.7.6", 89 | "sinon": "^2.1.0", 90 | "sinon-chai": "^2.8.0", 91 | "style-loader": "^0.18.2", 92 | "url-loader": "^0.5.8", 93 | "vue-loader": "^12.1.0", 94 | "vue-style-loader": "^3.0.1", 95 | "vue-template-compiler": "^2.3.3", 96 | "webpack": "^2.6.1", 97 | "webpack-bundle-analyzer": "^3.6.1", 98 | "webpack-dev-middleware": "^1.10.0", 99 | "webpack-hot-middleware": "^2.18.0", 100 | "webpack-merge": "^4.1.0" 101 | }, 102 | "engines": { 103 | "node": ">= 4.0.0", 104 | "npm": ">= 3.0.0" 105 | }, 106 | "browserslist": [ 107 | "> 1%", 108 | "last 2 versions", 109 | "not ie <= 8" 110 | ] 111 | } 112 | -------------------------------------------------------------------------------- /webpage/src/aspsm.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import { 4 | MainRoute, 5 | appRouter 6 | } from './router' 7 | import util from './libs/util' 8 | 9 | Vue.use(Vuex) 10 | const store = new Vuex.Store({ 11 | state: { 12 | // 数据库登录信息 13 | formItem: { 14 | input: '', 15 | sqladdress: '', 16 | sqlserver: '', 17 | basename: '', 18 | table_name: '', 19 | select: '', 20 | date: '' 21 | }, 22 | // 新的属性 23 | menuList: [], 24 | routers: [ 25 | MainRoute, ...appRouter 26 | ], 27 | currentPageName: 'home_index', 28 | currentPath: [{ 29 | title: '首页', 30 | path: '/', 31 | name: 'home_index' 32 | }], 33 | pageOpenedList: [{ 34 | title: '首页', 35 | path: '', 36 | name: 'home_index' 37 | }], 38 | tagsList: [...appRouter], 39 | cachePage: [] 40 | }, 41 | mutations: { 42 | clearAllTags (state) { 43 | state.pageOpenedList.splice(1) 44 | state.cachePage.length = 0 45 | localStorage.pageOpenedList = JSON.stringify(state.pageOpenedList) 46 | }, 47 | clearOtherTags (state, vm) { 48 | let currentName = vm.$route.name 49 | let currentIndex = 0 50 | state.pageOpenedList.forEach((item, index) => { 51 | if (item.name === currentName) { 52 | currentIndex = index 53 | } 54 | }) 55 | if (currentIndex === 0) { 56 | state.pageOpenedList.splice(1) 57 | } else { 58 | state.pageOpenedList.splice(currentIndex + 1) 59 | state.pageOpenedList.splice(1, currentIndex - 1) 60 | } 61 | let newCachepage = state.cachePage.filter(item => { 62 | return item === currentName 63 | }) 64 | state.cachePage = newCachepage 65 | localStorage.pageOpenedList = JSON.stringify(state.pageOpenedList) 66 | }, 67 | Menulist (state) { 68 | let accessCode = parseInt(sessionStorage.getItem('access')) // 0 69 | let menuList = [] 70 | appRouter.forEach((item, index) => { 71 | if (item.access !== undefined) { // item.access=0 72 | if (util.showThisRoute(item.access, accessCode)) { 73 | if (item.children.length <= 1) { 74 | menuList.push(item) 75 | } else { 76 | let i = menuList.push(item) 77 | let childrenArr = [] 78 | childrenArr = item.children.filter(child => { 79 | if (child.access !== undefined) { 80 | if (child.access === accessCode) { 81 | return child 82 | } 83 | } else { 84 | return child 85 | } 86 | }) 87 | menuList[i - 1].children = childrenArr 88 | } 89 | } 90 | } else { 91 | if (item.children.length <= 1) { 92 | menuList.push(item) 93 | } else { 94 | let i = menuList.push(item) 95 | let childrenArr = [] 96 | childrenArr = item.children.filter(child => { 97 | if (child.access !== undefined) { 98 | if (util.showThisRoute(child.access, accessCode)) { 99 | return child 100 | } 101 | } else { 102 | return child 103 | } 104 | }) 105 | menuList[i - 1].children = childrenArr 106 | } 107 | } 108 | }) 109 | state.menuList = menuList 110 | }, 111 | lock () { 112 | sessionStorage.setItem('locking', '1') 113 | }, 114 | unlock () { 115 | sessionStorage.setItem('locking', '0') 116 | }, 117 | Breadcrumbset (state, name) { 118 | if (name === 'ownspace_index') { 119 | state.currentPath.splice(1, state.currentPath.length - 1) 120 | state.currentPath.push({ 121 | 'title': '个人中心', 122 | 'path': 'ownspace', 123 | 'name': name 124 | }) 125 | } else if (name === 'home_index') { 126 | state.currentPath.splice(1, state.currentPath.length - 1) 127 | } else if (name === 'myorder') { 128 | state.currentPath.splice(1, state.currentPath.length - 1) 129 | state.currentPath.push({ 130 | 'title': '我的工单', 131 | 'path': 'message', 132 | 'name': name 133 | }) 134 | } else { 135 | state.currentPath.splice(1, state.currentPath.length - 1) 136 | appRouter.forEach((val) => { 137 | for (let i of val.children) { 138 | if (i.name === name) { 139 | state.currentPath.push({ 140 | 'title': val.title, 141 | 'path': val.path, 142 | 'name': val.name 143 | }, { 144 | 'title': i.title, 145 | 'path': `${val.path}/${i.path}`, 146 | 'name': i.name 147 | }) 148 | } 149 | } 150 | }) 151 | } 152 | }, 153 | removeTag (state, name) { 154 | state.pageOpenedList.map((item, index) => { 155 | if (item.name === name) { 156 | state.pageOpenedList.splice(index, 1) 157 | } 158 | }) 159 | } 160 | } 161 | }) 162 | 163 | export default store 164 | -------------------------------------------------------------------------------- /webpage/src/assets/alipay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/webpage/src/assets/alipay.jpg -------------------------------------------------------------------------------- /webpage/src/assets/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/webpage/src/assets/avatar.png -------------------------------------------------------------------------------- /webpage/src/assets/tablesmargintop.css: -------------------------------------------------------------------------------- 1 | .tabletop{ 2 | margin-top: 1% 3 | } 4 | 5 | .model_input{ 6 | width: 100%; 7 | margin-top: 2%; 8 | font-size: 14px 9 | } 10 | 11 | .model_p{ 12 | font-size: 14px; 13 | margin-top: 2%; 14 | } 15 | 16 | .div{ 17 | margin-top: 2%; 18 | margin-left: 10%; 19 | } 20 | .div_center{ 21 | width: 50%; 22 | margin-left: 10%; 23 | margin-top: 2%; 24 | } 25 | 26 | .div_display{ 27 | display: none; 28 | } 29 | 30 | .ivu-table .demo-table-info-row td{ 31 | background-color: #2db7f5; 32 | color: #000; 33 | } 34 | .ivu-table .demo-table-error-row td{ 35 | background-color: #ff9900; 36 | color: #000; 37 | } 38 | 39 | .ivu-table .demo-table-row td{ 40 | background-color: #bbbec4; 41 | color: #000; 42 | } 43 | .ivu-table td.demo-table-info-column{ 44 | background-color: #2db7f5; 45 | color: #fff; 46 | } 47 | .ivu-table .demo-table-info-cell-name { 48 | background-color: #2db7f5; 49 | color: #fff; 50 | } 51 | .ivu-table .demo-table-info-cell-age { 52 | background-color: #ff6600; 53 | color: #fff; 54 | } 55 | .ivu-table .demo-table-info-cell-address { 56 | background-color: #187; 57 | color: #fff; 58 | } 59 | 60 | .model_title { 61 | font-size: 13px; 62 | color: #1c2438; 63 | } 64 | 65 | .model_context { 66 | font-size: 15px; 67 | color: #80848f; 68 | } 69 | -------------------------------------------------------------------------------- /webpage/src/assets/wechat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/webpage/src/assets/wechat.jpg -------------------------------------------------------------------------------- /webpage/src/components/assistantManger/queryRecord.vue: -------------------------------------------------------------------------------- 1 | 5 | 36 | 132 | -------------------------------------------------------------------------------- /webpage/src/components/assistantManger/record.vue: -------------------------------------------------------------------------------- 1 | 5 | 35 | 128 | -------------------------------------------------------------------------------- /webpage/src/components/audit/expend.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 52 | 53 | 56 | -------------------------------------------------------------------------------- /webpage/src/components/error/401.css: -------------------------------------------------------------------------------- 1 | @keyframes error401animation { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 40% { 6 | transform: rotateZ(-20deg); 7 | } 8 | 45% { 9 | transform: rotateZ(-15deg); 10 | } 11 | 50% { 12 | transform: rotateZ(-20deg); 13 | } 14 | 55% { 15 | transform: rotateZ(-15deg); 16 | } 17 | 60% { 18 | transform: rotateZ(-20deg); 19 | } 20 | 100% { 21 | transform: rotateZ(0deg); 22 | } 23 | } 24 | .error401-body-con { 25 | width: 100%; 26 | height: 500px; 27 | position: absolute; 28 | left: 50%; 29 | top: 50%; 30 | transform: translate(-50%, -50%); 31 | } 32 | .error401-body-con-title { 33 | text-align: center; 34 | font-size: 240px; 35 | font-weight: 700; 36 | color: #2d8cf0; 37 | height: 260px; 38 | line-height: 260px; 39 | margin-top: 40px; 40 | } 41 | .error401-body-con-title .error401-0-span { 42 | display: inline-block; 43 | position: relative; 44 | width: 170px; 45 | height: 170px; 46 | border-radius: 50%; 47 | border: 20px solid #ed3f14; 48 | color: #ed3f14; 49 | margin-right: 10px; 50 | } 51 | .error401-body-con-title .error401-0-span i { 52 | display: inline-block; 53 | font-size: 120px; 54 | position: absolute; 55 | left: 50%; 56 | top: 50%; 57 | transform: translate(-50%, -50%); 58 | } 59 | .error401-body-con-title .error401-key-span { 60 | display: inline-block; 61 | position: relative; 62 | width: 100px; 63 | height: 170px; 64 | border-radius: 50%; 65 | margin-right: 10px; 66 | } 67 | .error401-body-con-title .error401-key-span i { 68 | display: inline-block; 69 | font-size: 190px; 70 | position: absolute; 71 | left: 20px; 72 | transform: translate(-50%, -50%); 73 | transform-origin: center bottom; 74 | animation: error401animation 2.8s ease 0s infinite; 75 | } 76 | .error401-body-con-message { 77 | display: block; 78 | text-align: center; 79 | font-size: 30px; 80 | font-weight: 500; 81 | letter-spacing: 4px; 82 | color: #dddde2; 83 | } 84 | .error401-btn-con { 85 | text-align: center; 86 | padding: 20px 0; 87 | margin-bottom: 40px; 88 | } 89 | -------------------------------------------------------------------------------- /webpage/src/components/error/401.less: -------------------------------------------------------------------------------- 1 | @keyframes error401animation { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 6 | 40% { 7 | transform: rotateZ(-20deg); 8 | } 9 | 10 | 45% { 11 | transform: rotateZ(-15deg); 12 | } 13 | 14 | 50% { 15 | transform: rotateZ(-20deg); 16 | } 17 | 18 | 55% { 19 | transform: rotateZ(-15deg); 20 | } 21 | 22 | 60% { 23 | transform: rotateZ(-20deg); 24 | } 25 | 26 | 100% { 27 | transform: rotateZ(0deg); 28 | } 29 | } 30 | 31 | .error401 { 32 | &-body-con { 33 | width: 100%; 34 | height: 500px; 35 | position: absolute; 36 | left: 50%; 37 | top: 50%; 38 | transform: translate(-50%,-50%); 39 | 40 | &-title { 41 | text-align: center; 42 | font-size: 240px; 43 | font-weight: 700; 44 | color: #2d8cf0; 45 | height: 260px; 46 | line-height: 260px; 47 | margin-top: 40px; 48 | 49 | .error401-0-span { 50 | display: inline-block; 51 | position: relative; 52 | width: 170px; 53 | height: 170px; 54 | border-radius: 50%; 55 | border: 20px solid #ed3f14; 56 | color: #ed3f14; 57 | margin-right: 10px; 58 | 59 | i { 60 | display: inline-block; 61 | font-size: 120px; 62 | position: absolute; 63 | left: 50%; 64 | top: 50%; 65 | transform: translate(-50%,-50%); 66 | } 67 | } 68 | 69 | .error401-key-span { 70 | display: inline-block; 71 | position: relative; 72 | width: 100px; 73 | height: 170px; 74 | border-radius: 50%; 75 | margin-right: 10px; 76 | 77 | i { 78 | display: inline-block; 79 | font-size: 190px; 80 | position: absolute; 81 | left: 20px; 82 | transform: translate(-50%,-50%); 83 | transform-origin: center bottom; 84 | animation: error401animation 2.8s ease 0s infinite; 85 | } 86 | } 87 | } 88 | 89 | &-message { 90 | display: block; 91 | text-align: center; 92 | font-size: 30px; 93 | font-weight: 500; 94 | letter-spacing: 4px; 95 | color: #dddde2; 96 | } 97 | } 98 | 99 | &-btn-con { 100 | text-align: center; 101 | padding: 20px 0; 102 | margin-bottom: 40px; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /webpage/src/components/error/401.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | 21 | 36 | -------------------------------------------------------------------------------- /webpage/src/components/error/404.css: -------------------------------------------------------------------------------- 1 | @keyframes error404animation { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 20% { 6 | transform: rotateZ(-60deg); 7 | } 8 | 40% { 9 | transform: rotateZ(-10deg); 10 | } 11 | 60% { 12 | transform: rotateZ(50deg); 13 | } 14 | 80% { 15 | transform: rotateZ(-20deg); 16 | } 17 | 100% { 18 | transform: rotateZ(0deg); 19 | } 20 | } 21 | .error404-body-con { 22 | width: 100%; 23 | height: 500px; 24 | position: absolute; 25 | left: 50%; 26 | top: 50%; 27 | transform: translate(-50%, -50%); 28 | } 29 | .error404-body-con-title { 30 | text-align: center; 31 | font-size: 240px; 32 | font-weight: 700; 33 | color: #2d8cf0; 34 | height: 260px; 35 | line-height: 260px; 36 | margin-top: 40px; 37 | } 38 | .error404-body-con-title span { 39 | display: inline-block; 40 | color: #19be6b; 41 | font-size: 230px; 42 | animation: error404animation 3s ease 0s infinite alternate; 43 | } 44 | .error404-body-con-message { 45 | display: block; 46 | text-align: center; 47 | font-size: 30px; 48 | font-weight: 500; 49 | letter-spacing: 12px; 50 | color: #dddde2; 51 | } 52 | .error404-btn-con { 53 | text-align: center; 54 | padding: 20px 0; 55 | margin-bottom: 40px; 56 | } 57 | -------------------------------------------------------------------------------- /webpage/src/components/error/404.less: -------------------------------------------------------------------------------- 1 | @keyframes error404animation { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 6 | 20% { 7 | transform: rotateZ(-60deg); 8 | } 9 | 10 | 40% { 11 | transform: rotateZ(-10deg); 12 | } 13 | 14 | 60% { 15 | transform: rotateZ(50deg); 16 | } 17 | 18 | 80% { 19 | transform: rotateZ(-20deg); 20 | } 21 | 22 | 100% { 23 | transform: rotateZ(0deg); 24 | } 25 | } 26 | 27 | .error404 { 28 | &-body-con { 29 | width: 100%; 30 | height: 500px; 31 | position: absolute; 32 | left: 50%; 33 | top: 50%; 34 | transform: translate(-50%,-50%); 35 | 36 | &-title { 37 | text-align: center; 38 | font-size: 240px; 39 | font-weight: 700; 40 | color: #2d8cf0; 41 | height: 260px; 42 | line-height: 260px; 43 | margin-top: 40px; 44 | 45 | span { 46 | display: inline-block; 47 | color: #19be6b; 48 | font-size: 230px; 49 | animation: error404animation 3s ease 0s infinite alternate; 50 | } 51 | } 52 | 53 | &-message { 54 | display: block; 55 | text-align: center; 56 | font-size: 30px; 57 | font-weight: 500; 58 | letter-spacing: 12px; 59 | color: #dddde2; 60 | } 61 | } 62 | 63 | &-btn-con { 64 | text-align: center; 65 | padding: 20px 0; 66 | margin-bottom: 40px; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /webpage/src/components/error/404.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | 35 | -------------------------------------------------------------------------------- /webpage/src/components/error/500.css: -------------------------------------------------------------------------------- 1 | @keyframes error500animation { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 20% { 6 | transform: rotateZ(-10deg); 7 | } 8 | 40% { 9 | transform: rotateZ(5deg); 10 | } 11 | 60% { 12 | transform: rotateZ(-5deg); 13 | } 14 | 80% { 15 | transform: rotateZ(10deg); 16 | } 17 | 100% { 18 | transform: rotateZ(0deg); 19 | } 20 | } 21 | .error500-body-con { 22 | width: 100%; 23 | height: 500px; 24 | position: absolute; 25 | left: 50%; 26 | top: 50%; 27 | transform: translate(-50%, -50%); 28 | } 29 | .error500-body-con-title { 30 | text-align: center; 31 | font-size: 240px; 32 | font-weight: 700; 33 | color: #2d8cf0; 34 | height: 260px; 35 | line-height: 260px; 36 | margin-top: 40px; 37 | } 38 | .error500-body-con-title .error500-0-span { 39 | display: inline-block; 40 | position: relative; 41 | width: 170px; 42 | height: 170px; 43 | border-radius: 50%; 44 | border: 20px solid #ed3f14; 45 | color: #ed3f14; 46 | margin-right: 10px; 47 | } 48 | .error500-body-con-title .error500-0-span i { 49 | display: inline-block; 50 | font-size: 120px; 51 | position: absolute; 52 | bottom: -10px; 53 | left: 10px; 54 | transform-origin: center bottom; 55 | animation: error500animation 3s ease 0s infinite alternate; 56 | } 57 | .error500-body-con-message { 58 | display: block; 59 | text-align: center; 60 | font-size: 30px; 61 | font-weight: 500; 62 | letter-spacing: 4px; 63 | color: #dddde2; 64 | } 65 | .error500-btn-con { 66 | text-align: center; 67 | padding: 20px 0; 68 | margin-bottom: 40px; 69 | } 70 | -------------------------------------------------------------------------------- /webpage/src/components/error/500.less: -------------------------------------------------------------------------------- 1 | @keyframes error500animation { 2 | 0% { 3 | transform: rotateZ(0deg); 4 | } 5 | 6 | 20% { 7 | transform: rotateZ(-10deg); 8 | } 9 | 10 | 40% { 11 | transform: rotateZ(5deg); 12 | } 13 | 14 | 60% { 15 | transform: rotateZ(-5deg); 16 | } 17 | 18 | 80% { 19 | transform: rotateZ(10deg); 20 | } 21 | 22 | 100% { 23 | transform: rotateZ(0deg); 24 | } 25 | } 26 | 27 | .error500 { 28 | &-body-con { 29 | width: 100%; 30 | height: 500px; 31 | position: absolute; 32 | left: 50%; 33 | top: 50%; 34 | transform: translate(-50%,-50%); 35 | 36 | &-title { 37 | text-align: center; 38 | font-size: 240px; 39 | font-weight: 700; 40 | color: #2d8cf0; 41 | height: 260px; 42 | line-height: 260px; 43 | margin-top: 40px; 44 | 45 | .error500-0-span { 46 | display: inline-block; 47 | position: relative; 48 | width: 170px; 49 | height: 170px; 50 | border-radius: 50%; 51 | border: 20px solid #ed3f14; 52 | color: #ed3f14; 53 | margin-right: 10px; 54 | 55 | i { 56 | display: inline-block; 57 | font-size: 120px; 58 | position: absolute; 59 | bottom: -10px; 60 | left: 10px; 61 | transform-origin: center bottom; 62 | animation: error500animation 3s ease 0s infinite alternate; 63 | } 64 | } 65 | } 66 | 67 | &-message { 68 | display: block; 69 | text-align: center; 70 | font-size: 30px; 71 | font-weight: 500; 72 | letter-spacing: 4px; 73 | color: #dddde2; 74 | } 75 | } 76 | 77 | &-btn-con { 78 | text-align: center; 79 | padding: 20px 0; 80 | margin-bottom: 40px; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /webpage/src/components/error/500.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 22 | 23 | 38 | -------------------------------------------------------------------------------- /webpage/src/components/home/components/countUp.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 101 | -------------------------------------------------------------------------------- /webpage/src/components/home/components/dataSourcePie.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 71 | -------------------------------------------------------------------------------- /webpage/src/components/home/components/inforCard.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 23 | 24 | 53 | -------------------------------------------------------------------------------- /webpage/src/components/home/components/styles/infor-card.css: -------------------------------------------------------------------------------- 1 | .infor-card-icon-con { 2 | height: 100%; 3 | } 4 | .height-100 { 5 | height: 100%; 6 | } 7 | .infor-card-con { 8 | height: 100px; 9 | } 10 | .infor-intro-text { 11 | font-size: 12px; 12 | font-weight: 500; 13 | color: #C8C8C8; 14 | } 15 | -------------------------------------------------------------------------------- /webpage/src/components/home/components/styles/infor-card.less: -------------------------------------------------------------------------------- 1 | .infor-card-icon-con { 2 | height: 100%; 3 | } 4 | 5 | .height-100 { 6 | height: 100%; 7 | } 8 | 9 | .infor-card-con { 10 | height: 100px; 11 | } 12 | 13 | .infor-intro-text { 14 | font-size: 12px; 15 | font-weight: 500; 16 | color: #C8C8C8; 17 | } 18 | -------------------------------------------------------------------------------- /webpage/src/components/home/components/styles/to-do-list-item.css: -------------------------------------------------------------------------------- 1 | .to-do-list-item-text { 2 | word-break: keep-all; 3 | white-space: nowrap; 4 | overflow: hidden; 5 | text-overflow: ellipsis; 6 | font-weight: 500; 7 | cursor: pointer; 8 | height: 36px; 9 | } 10 | .to-do-list-item-text .height-100 { 11 | height: 100%; 12 | } 13 | .to-do-list-item-text .infor-icon-row { 14 | color: #c8c8c8; 15 | } 16 | .hasDid { 17 | text-decoration: line-through; 18 | color: gray; 19 | font-weight: 100; 20 | } 21 | -------------------------------------------------------------------------------- /webpage/src/components/home/components/styles/to-do-list-item.less: -------------------------------------------------------------------------------- 1 | .to-do-list-item-text { 2 | word-break: keep-all; 3 | white-space: nowrap; 4 | overflow: hidden; 5 | text-overflow: ellipsis; 6 | font-weight: 500; 7 | cursor: pointer; 8 | height: 36px; 9 | 10 | .height-100 { 11 | height: 100%; 12 | } 13 | 14 | .infor-icon-row { 15 | color: #c8c8c8; 16 | } 17 | } 18 | 19 | .hasDid { 20 | text-decoration: line-through; 21 | color: gray; 22 | font-weight: 100; 23 | } 24 | -------------------------------------------------------------------------------- /webpage/src/components/home/components/toDoListItem.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | 20 | 42 | -------------------------------------------------------------------------------- /webpage/src/components/home/home.css: -------------------------------------------------------------------------------- 1 | .user-infor { 2 | height: 120px; 3 | } 4 | .avator-img { 5 | display: block; 6 | width: 80%; 7 | max-width: 100px; 8 | height: auto; 9 | } 10 | .card-user-infor-name { 11 | font-size: 2em; 12 | color: #2d8cf0; 13 | } 14 | .card-title { 15 | color: #abafbd; 16 | } 17 | .made-child-con-middle { 18 | height: 100%; 19 | } 20 | .to-do-list-con { 21 | height: 300px; 22 | overflow: auto; 23 | } 24 | .to-do-item { 25 | padding: 2px; 26 | } 27 | .infor-card-con { 28 | height: 100px; 29 | } 30 | .infor-card-icon-con { 31 | height: 100%; 32 | color: white; 33 | border-radius: 3px 0 0 3px; 34 | } 35 | .map-con { 36 | height: 305px; 37 | } 38 | .map-incon { 39 | height: 100%; 40 | } 41 | .data-source-row { 42 | height: 200px; 43 | } 44 | .line-chart-con { 45 | height: 150px; 46 | } 47 | -------------------------------------------------------------------------------- /webpage/src/components/home/home.less: -------------------------------------------------------------------------------- 1 | .user-infor { 2 | height: 120px; 3 | } 4 | 5 | .avator-img { 6 | display: block; 7 | width: 80%; 8 | max-width: 100px; 9 | height: auto; 10 | } 11 | 12 | .card-user-infor-name { 13 | font-size: 2em; 14 | color: #2d8cf0; 15 | } 16 | 17 | .card-title { 18 | color: #abafbd; 19 | } 20 | 21 | .made-child-con-middle { 22 | height: 100%; 23 | } 24 | 25 | .to-do-list-con { 26 | height: 300px; 27 | overflow: auto; 28 | } 29 | 30 | .to-do-item { 31 | padding: 2px; 32 | } 33 | 34 | .infor-card-con { 35 | height: 100px; 36 | } 37 | 38 | .infor-card-icon-con { 39 | height: 100%; 40 | color: white; 41 | border-radius: 3px 0 0 3px; 42 | } 43 | 44 | .map-con { 45 | height: 305px; 46 | } 47 | 48 | .map-incon { 49 | height: 100%; 50 | } 51 | 52 | .data-source-row { 53 | height: 200px; 54 | } 55 | 56 | .line-chart-con { 57 | height: 150px; 58 | } 59 | -------------------------------------------------------------------------------- /webpage/src/components/home/home.vue: -------------------------------------------------------------------------------- 1 | 21 | 115 | 116 | 170 | -------------------------------------------------------------------------------- /webpage/src/components/order/components/table.css: -------------------------------------------------------------------------------- 1 | .dragging-tip-enter-active { 2 | opacity: 1; 3 | transition: opacity 0.3s; 4 | } 5 | .dragging-tip-enter, 6 | .dragging-tip-leave-to { 7 | opacity: 0; 8 | transition: opacity 0.3s; 9 | } 10 | .dragging-tip-con { 11 | display: block; 12 | text-align: center; 13 | width: 100%; 14 | height: 50px; 15 | } 16 | .dragging-tip-con span { 17 | font-size: 18px; 18 | } 19 | .record-tip-con { 20 | display: block; 21 | width: 100%; 22 | height: 292px; 23 | overflow: auto; 24 | } 25 | .record-item { 26 | box-sizing: content-box; 27 | display: block; 28 | overflow: hidden; 29 | height: 24px; 30 | line-height: 24px; 31 | padding: 8px 10px; 32 | border-bottom: 1px dashed gainsboro; 33 | } 34 | .record-tip-con span { 35 | font-size: 14px; 36 | } 37 | .edittable-test-con { 38 | min-height: 600px; 39 | } 40 | .edittable-testauto-con { 41 | height: 100%; 42 | } 43 | .edittable-table-height-con { 44 | min-height: 600px; 45 | } 46 | .edittable-table-height1-con { 47 | height: 200px; 48 | } 49 | .edittable-con-1 { 50 | box-sizing: content-box; 51 | padding: 15px 0 0; 52 | height: 550px; 53 | } 54 | .exportable-table-download-con1 { 55 | padding: 16px 0 16px 20px; 56 | border-bottom: 1px dashed #c3c3c3; 57 | margin-bottom: 16px; 58 | } 59 | .exportable-table-download-con2 { 60 | padding-left: 20px; 61 | } 62 | .show-image { 63 | padding: 20px 0; 64 | } 65 | .show-image img { 66 | display: block; 67 | width: 100%; 68 | height: auto; 69 | } 70 | -------------------------------------------------------------------------------- /webpage/src/components/order/components/table.less: -------------------------------------------------------------------------------- 1 | .dragging-tip-enter-active { 2 | opacity: 1; 3 | transition: opacity 0.3s; 4 | } 5 | 6 | .dragging-tip-enter, 7 | .dragging-tip-leave-to { 8 | opacity: 0; 9 | transition: opacity 0.3s; 10 | } 11 | 12 | .dragging-tip-con { 13 | display: block; 14 | text-align: center; 15 | width: 100%; 16 | height: 50px; 17 | } 18 | 19 | .dragging-tip-con span { 20 | font-size: 18px; 21 | } 22 | 23 | .record-tip-con { 24 | display: block; 25 | width: 100%; 26 | height: 292px; 27 | overflow: auto; 28 | } 29 | 30 | .record-item { 31 | box-sizing: content-box; 32 | display: block; 33 | overflow: hidden; 34 | height: 24px; 35 | line-height: 24px; 36 | padding: 8px 10px; 37 | border-bottom: 1px dashed gainsboro; 38 | } 39 | 40 | .record-tip-con span { 41 | font-size: 14px; 42 | } 43 | 44 | .edittable-test-con { 45 | min-height: 600px; 46 | } 47 | 48 | .edittable-testauto-con { 49 | height: 100%; 50 | } 51 | 52 | .edittable-table-height-con { 53 | min-height: 600px; 54 | } 55 | 56 | .edittable-table-height1-con { 57 | height: 200px; 58 | } 59 | 60 | .edittable-con-1 { 61 | box-sizing: content-box; 62 | padding: 15px 0 0; 63 | height: 550px; 64 | } 65 | 66 | .exportable-table-download-con1 { 67 | padding: 16px 0 16px 20px; 68 | border-bottom: 1px dashed #c3c3c3; 69 | margin-bottom: 16px; 70 | } 71 | 72 | .exportable-table-download-con2 { 73 | padding-left: 20px; 74 | } 75 | 76 | .show-image { 77 | padding: 20px 0; 78 | } 79 | 80 | .show-image img { 81 | display: block; 82 | width: 100%; 83 | height: auto; 84 | } 85 | -------------------------------------------------------------------------------- /webpage/src/components/order/myOrder.vue: -------------------------------------------------------------------------------- 1 | 5 | 44 | 191 | 192 | -------------------------------------------------------------------------------- /webpage/src/components/personalCenter/own-space.css: -------------------------------------------------------------------------------- 1 | .own-space-btn-box { 2 | margin-bottom: 10px; 3 | } 4 | .own-space-btn-box button { 5 | padding-left: 0; 6 | } 7 | .own-space-btn-box button span { 8 | color: #2D8CF0; 9 | transition: all 0.2s; 10 | } 11 | .own-space-btn-box button span:hover { 12 | color: #0C25F1; 13 | transition: all 0.2s; 14 | } 15 | .own-space-tra { 16 | width: 10px; 17 | height: 10px; 18 | transform: rotate(45deg); 19 | position: absolute; 20 | top: 50%; 21 | margin-top: -6px; 22 | left: -3px; 23 | box-shadow: 0 0 2px 3px rgba(0, 0, 0, 0.1); 24 | background-color: white; 25 | z-index: 100; 26 | } 27 | .own-space-input-identifycode-con { 28 | position: absolute; 29 | width: 200px; 30 | height: 100px; 31 | right: -220px; 32 | top: 50%; 33 | margin-top: -50px; 34 | border-radius: 4px; 35 | box-shadow: 0 0 2px 3px rgba(0, 0, 0, 0.1); 36 | } 37 | -------------------------------------------------------------------------------- /webpage/src/components/personalCenter/own-space.less: -------------------------------------------------------------------------------- 1 | .own-space { 2 | &-btn-box { 3 | margin-bottom: 10px; 4 | 5 | button { 6 | padding-left: 0; 7 | 8 | span { 9 | color: #2D8CF0; 10 | transition: all 0.2s; 11 | } 12 | 13 | span:hover { 14 | color: #0C25F1; 15 | transition: all 0.2s; 16 | } 17 | } 18 | } 19 | 20 | &-tra { 21 | width: 10px; 22 | height: 10px; 23 | transform: rotate(45deg); 24 | position: absolute; 25 | top: 50%; 26 | margin-top: -6px; 27 | left: -3px; 28 | box-shadow: 0 0 2px 3px rgba(0,0,0,.1); 29 | background-color: white; 30 | z-index: 100; 31 | } 32 | 33 | &-input-identifycode-con { 34 | position: absolute; 35 | width: 200px; 36 | height: 100px; 37 | right: -220px; 38 | top: 50%; 39 | margin-top: -50px; 40 | border-radius: 4px; 41 | box-shadow: 0 0 2px 3px rgba(0,0,0,.1); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /webpage/src/components/query/submitPage.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 85 | 86 | 109 | -------------------------------------------------------------------------------- /webpage/src/libs/editor.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 68 | 69 | 71 | -------------------------------------------------------------------------------- /webpage/src/main.css: -------------------------------------------------------------------------------- 1 | .main { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | .main .unlock-con { 7 | width: 0; 8 | height: 0; 9 | position: absolute; 10 | left: 50%; 11 | top: 50%; 12 | z-index: 11000; 13 | } 14 | .main .sidebar-menu-con { 15 | height: 100%; 16 | position: fixed; 17 | top: 0; 18 | left: 0; 19 | z-index: 1010; 20 | } 21 | .main .main-hide-text .layout-text { 22 | display: none; 23 | } 24 | .main-content-container { 25 | position: relative; 26 | } 27 | .main-header-con { 28 | box-sizing: border-box; 29 | position: fixed; 30 | display: block; 31 | padding-left: 200px; 32 | width: 100%; 33 | height: 100px; 34 | z-index: 1000; 35 | box-shadow: 0 2px 1px 1px rgba(100, 100, 100, 0.1); 36 | } 37 | .main-breadcrumb { 38 | padding: 8px 15px 0; 39 | } 40 | .main-menu-left { 41 | background: #464c5b; 42 | height: 100%; 43 | } 44 | .main .tags-con { 45 | height: 40px; 46 | padding: 2px 10px; 47 | z-index: -1; 48 | overflow: hidden; 49 | background: #f0f0f0; 50 | } 51 | .main .tags-con .close-all-tag-con { 52 | position: absolute; 53 | right: 0; 54 | top: 62%; 55 | box-sizing: border-box; 56 | padding-top: 8px; 57 | text-align: center; 58 | width: 110px; 59 | height: 38%; 60 | background: white; 61 | box-shadow: -3px 0 15px 3px rgba(0, 0, 0, 0.1); 62 | z-index: 4; 63 | } 64 | .main-header { 65 | height: 60px; 66 | background: #fff; 67 | box-shadow: 0 2px 1px 1px rgba(100, 100, 100, 0.1); 68 | position: relative; 69 | z-index: 10; 70 | } 71 | .main-header .navicon-con { 72 | margin: 6px; 73 | display: inline-block; 74 | } 75 | .main-header .header-middle-con { 76 | position: absolute; 77 | left: 60px; 78 | top: 0; 79 | right: 340px; 80 | bottom: 0; 81 | padding: 10px; 82 | overflow: hidden; 83 | } 84 | .main-header .header-avator-con { 85 | position: absolute; 86 | right: 0; 87 | top: 0; 88 | height: 100%; 89 | width: 300px; 90 | } 91 | .main-header .header-avator-con .switch-theme-con { 92 | display: inline-block; 93 | width: 30px; 94 | height: 100%; 95 | } 96 | .main-header .header-avator-con .message-con { 97 | display: inline-block; 98 | width: 30px; 99 | padding: 18px 0; 100 | text-align: center; 101 | cursor: pointer; 102 | } 103 | .main-header .header-avator-con .message-con i { 104 | vertical-align: middle; 105 | } 106 | .main-header .header-avator-con .change-skin { 107 | font-size: 14px; 108 | font-weight: 500; 109 | padding-right: 5px; 110 | } 111 | .main-header .header-avator-con .switch-theme { 112 | height: 100%; 113 | } 114 | .main-header .header-avator-con .user-dropdown-menu-con { 115 | position: absolute; 116 | right: 0; 117 | top: 0; 118 | width: 170px; 119 | height: 100%; 120 | z-index: 5; 121 | } 122 | .main-header .header-avator-con .user-dropdown-innercon { 123 | height: 100%; 124 | padding-right: 6px; 125 | } 126 | .main-header .header-avator-con .full-screen-btn-con { 127 | display: inline-block; 128 | width: 30px; 129 | padding: 18px 0; 130 | text-align: center; 131 | cursor: pointer; 132 | } 133 | .main-header .header-avator-con .full-screen-btn-con i { 134 | vertical-align: middle; 135 | } 136 | .main-header .header-avator-con .lock-screen-btn-con { 137 | display: inline-block; 138 | width: 30px; 139 | padding: 18px 0; 140 | text-align: center; 141 | cursor: pointer; 142 | } 143 | .main-header .header-avator-con .lock-screen-btn-con i { 144 | vertical-align: middle; 145 | } 146 | .main .single-page-con { 147 | box-sizing: border-box; 148 | padding: 100px 0 10px; 149 | background-color: #F0F0F0; 150 | z-index: -1; 151 | } 152 | .main .single-page-con .single-page { 153 | margin: 10px; 154 | } 155 | .main-copy { 156 | text-align: center; 157 | padding: 10px 0 20px; 158 | color: #9ea7b4; 159 | } 160 | .taglist-moving-animation-move { 161 | transition: transform 0.3s; 162 | } 163 | .logo-con { 164 | padding: 8px; 165 | } 166 | .logo-con img { 167 | width: 100%; 168 | } 169 | -------------------------------------------------------------------------------- /webpage/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 Subnet from './subnet.vue' 5 | import iView from 'iview' 6 | import Vuex from 'vuex' 7 | import VueRouter from 'vue-router' 8 | import axios from 'axios' 9 | import { MainRoute } from './router' 10 | import store from './aspsm' 11 | import 'iview/dist/styles/iview.css' 12 | import config from './libs/util' 13 | import particles from 'particles.js/particles' 14 | 15 | Vue.config.productionTip = false 16 | Vue.prototype.$config = config 17 | Vue.use(particles) 18 | Vue.use(Vuex) 19 | Vue.use(iView) 20 | Vue.use(VueRouter) 21 | Vue.prototype.$http = axios 22 | /* eslint-disable no-new */ 23 | const RouterConfig = { 24 | routes: MainRoute 25 | } 26 | 27 | const router = new VueRouter(RouterConfig) 28 | 29 | router.beforeEach((to, from, next) => { 30 | iView.LoadingBar.start() 31 | config.title(to.meta.title) 32 | if (sessionStorage.getItem('locking') === '1' && to.name !== 'locking') { // 判断当前是否是锁定状态 33 | next(false) 34 | router.replace({name: 'login'}) 35 | } else { 36 | if (!sessionStorage.getItem('user') && to.name !== 'login') { // 判断是否已经登录且前往的页面不是登录页 37 | next(false) 38 | router.replace({name: 'login'}) 39 | } else { 40 | next() 41 | } 42 | } 43 | }) 44 | 45 | router.afterEach(() => { 46 | iView.LoadingBar.finish() 47 | window.scrollTo(0, 0) 48 | }) 49 | 50 | new Vue({ 51 | el: '#Subnet', 52 | template: '', 53 | components: {Subnet}, 54 | store: store, 55 | router: router 56 | }) 57 | -------------------------------------------------------------------------------- /webpage/src/main.less: -------------------------------------------------------------------------------- 1 | .main { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | 6 | .unlock-con { 7 | width: 0; 8 | height: 0; 9 | position: absolute; 10 | left: 50%; 11 | top: 50%; 12 | z-index: 11000; 13 | } 14 | 15 | .sidebar-menu-con { 16 | height: 100%; 17 | position: fixed; 18 | top: 0; 19 | left: 0; 20 | z-index: 1010; 21 | } 22 | 23 | .main-hide-text .layout-text { 24 | display: none; 25 | } 26 | 27 | &-content-container { 28 | position: relative; 29 | } 30 | 31 | &-header-con { 32 | box-sizing: border-box; 33 | position: fixed; 34 | display: block; 35 | padding-left: 200px; 36 | width: 100%; 37 | height: 100px; 38 | z-index: 1000; 39 | box-shadow: 0 2px 1px 1px rgba(100,100,100,.1); 40 | } 41 | 42 | &-breadcrumb { 43 | padding: 8px 15px 0; 44 | } 45 | 46 | &-menu-left { 47 | background: #464c5b; 48 | height: 100%; 49 | } 50 | 51 | .tags-con { 52 | height: 40px; 53 | padding: 2px 10px; 54 | z-index: -1; 55 | overflow: hidden; 56 | background: #f0f0f0; 57 | .close-all-tag-con{ 58 | position: absolute; 59 | right: 0; 60 | top: 62%; 61 | box-sizing: border-box; 62 | padding-top: 8px; 63 | text-align: center; 64 | width: 110px; 65 | height: 38%; 66 | background: white; 67 | box-shadow: -3px 0 15px 3px rgba(0, 0, 0, .1); 68 | z-index: 4; 69 | } 70 | } 71 | 72 | &-header { 73 | height: 60px; 74 | background: #fff; 75 | box-shadow: 0 2px 1px 1px rgba(100,100,100,.1); 76 | position: relative; 77 | z-index: 10; 78 | 79 | .navicon-con { 80 | margin: 6px; 81 | display: inline-block; 82 | } 83 | 84 | .header-middle-con { 85 | position: absolute; 86 | left: 60px; 87 | top: 0; 88 | right: 340px; 89 | bottom: 0; 90 | padding: 10px; 91 | overflow: hidden; 92 | } 93 | 94 | .header-avator-con { 95 | position: absolute; 96 | right: 0; 97 | top: 0; 98 | height: 100%; 99 | width:300px; 100 | 101 | .switch-theme-con { 102 | display: inline-block; 103 | width: 30px; 104 | height: 100%; 105 | } 106 | 107 | .message-con { 108 | display: inline-block; 109 | width: 30px; 110 | padding: 18px 0; 111 | text-align: center; 112 | cursor: pointer; 113 | 114 | i { 115 | vertical-align: middle; 116 | } 117 | } 118 | 119 | .change-skin { 120 | font-size: 14px; 121 | font-weight: 500; 122 | padding-right: 5px; 123 | } 124 | 125 | .switch-theme { 126 | height: 100%; 127 | } 128 | 129 | .user-dropdown { 130 | &-menu-con { 131 | position: absolute; 132 | right: 0; 133 | top: 0; 134 | width: 170px; 135 | height: 100%; 136 | z-index: 5; 137 | } 138 | 139 | &-innercon { 140 | height: 100%; 141 | padding-right: 6px; 142 | } 143 | } 144 | 145 | .full-screen-btn-con { 146 | display: inline-block; 147 | width: 30px; 148 | padding: 18px 0; 149 | text-align: center; 150 | cursor: pointer; 151 | 152 | i { 153 | vertical-align: middle; 154 | } 155 | } 156 | 157 | .lock-screen-btn-con { 158 | display: inline-block; 159 | width: 30px; 160 | padding: 18px 0; 161 | text-align: center; 162 | cursor: pointer; 163 | 164 | i { 165 | vertical-align: middle; 166 | } 167 | } 168 | } 169 | } 170 | 171 | .single-page-con { 172 | box-sizing: border-box; 173 | padding: 100px 0 10px; 174 | background-color: #F0F0F0; 175 | z-index: -1; 176 | 177 | .single-page { 178 | margin: 10px; 179 | } 180 | } 181 | 182 | &-copy { 183 | text-align: center; 184 | padding: 10px 0 20px; 185 | color: #9ea7b4; 186 | } 187 | } 188 | 189 | .taglist-moving-animation-move { 190 | transition: transform 0.3s; 191 | } 192 | 193 | .logo-con { 194 | padding: 8px; 195 | 196 | img { 197 | width: 100%; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /webpage/src/main_components/breadcrumbNav.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /webpage/src/main_components/locking-page.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 39 | -------------------------------------------------------------------------------- /webpage/src/main_components/sidebarMenu.vue: -------------------------------------------------------------------------------- 1 | 14 | 51 | 113 | -------------------------------------------------------------------------------- /webpage/src/main_components/sidebarMenuShrink.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 61 | 62 | 123 | -------------------------------------------------------------------------------- /webpage/src/main_components/tagsPageOpened.vue: -------------------------------------------------------------------------------- 1 | 4 | 31 | 32 | 92 | -------------------------------------------------------------------------------- /webpage/src/main_components/unlock.css: -------------------------------------------------------------------------------- 1 | .unlock-body-con { 2 | position: absolute; 3 | width: 400px; 4 | height: 100px; 5 | left: 50%; 6 | top: 50%; 7 | margin-left: -200px; 8 | margin-top: -200px; 9 | transform-origin: center center; 10 | z-index: 10; 11 | } 12 | .unlock-body-con .unlock-avator-con { 13 | position: absolute; 14 | left: 50%; 15 | top: 50%; 16 | transform: translate(-50%, -50%); 17 | box-sizing: border-box; 18 | width: 100px; 19 | height: 100px; 20 | border-radius: 50%; 21 | overflow: hidden; 22 | border: 2px solid white; 23 | cursor: pointer; 24 | transition: all 0.5s; 25 | z-index: 12; 26 | box-shadow: 0 0 10px 2px rgba(255, 255, 255, 0.3); 27 | } 28 | .unlock-body-con .unlock-avator-con .unlock-avator-img { 29 | width: 100%; 30 | height: 100%; 31 | display: block; 32 | z-index: 7; 33 | } 34 | .unlock-body-con .unlock-avator-con .unlock-avator-cover { 35 | width: 100%; 36 | height: 100%; 37 | background: rgba(0, 0, 0, 0.6); 38 | z-index: 11600; 39 | position: absolute; 40 | left: 0; 41 | top: 0; 42 | opacity: 0; 43 | transition: opacity 0.2s; 44 | color: white; 45 | } 46 | .unlock-body-con .unlock-avator-con .unlock-avator-cover span { 47 | display: block; 48 | margin: 20px auto 5px; 49 | text-align: center; 50 | } 51 | .unlock-body-con .unlock-avator-con .unlock-avator-cover p { 52 | text-align: center; 53 | font-size: 16px; 54 | font-weight: 500; 55 | } 56 | .unlock-body-con .unlock-avator-con:hover .unlock-avator-cover { 57 | opacity: 1; 58 | transition: opacity 0.2s; 59 | } 60 | .unlock-body-con .unlock-avator-under-back { 61 | position: absolute; 62 | left: 50%; 63 | top: 50%; 64 | transform: translate(-45px, -50%); 65 | box-sizing: border-box; 66 | width: 100px; 67 | height: 100px; 68 | border-radius: 50%; 69 | background: #667aa6; 70 | transition: all 0.5s; 71 | z-index: 5; 72 | } 73 | .unlock-body-con .unlock-input-con { 74 | position: absolute; 75 | height: 70px; 76 | width: 350px; 77 | top: 15px; 78 | right: 0; 79 | } 80 | .unlock-body-con .unlock-input-con .unlock-input-overflow-con { 81 | position: absolute; 82 | width: 100%; 83 | height: 100%; 84 | left: 0; 85 | top: 0; 86 | overflow: hidden; 87 | } 88 | .unlock-body-con .unlock-input-con .unlock-input-overflow-con .unlock-overflow-body { 89 | position: absolute; 90 | top: 0; 91 | right: 0; 92 | width: 100%; 93 | height: 100%; 94 | transition: all 0.5s ease 0.5s; 95 | } 96 | .unlock-body-con .unlock-input-con .unlock-input-overflow-con .unlock-overflow-body .unlock-input { 97 | float: left; 98 | display: block; 99 | box-sizing: content-box; 100 | height: 22px; 101 | width: 230px; 102 | font-size: 18px; 103 | outline: none; 104 | padding: 11px 10px 11px 30px; 105 | border: 2px solid #e2ddde; 106 | margin-top: 10px; 107 | } 108 | .unlock-body-con .unlock-input-con .unlock-input-overflow-con .unlock-overflow-body .unlock-btn { 109 | float: left; 110 | display: block; 111 | font-size: 20px; 112 | padding: 7px 30px; 113 | cursor: pointer; 114 | border-radius: 0 25px 25px 0; 115 | border: 2px solid #e2ddde; 116 | border-left: none; 117 | background: #2d8cf0; 118 | outline: none; 119 | transition: all 0.2s; 120 | margin-top: 10px; 121 | } 122 | .unlock-body-con .unlock-input-con .unlock-input-overflow-con .unlock-overflow-body .unlock-btn:hover { 123 | background: #5cadff; 124 | box-shadow: 0 0 10px 3px rgba(255, 255, 255, 0.2); 125 | } 126 | .unlock-body-con .unlock-input-con .unlock-input-overflow-con .unlock-overflow-body .click-unlock-btn { 127 | background: #2b85e4 !important; 128 | } 129 | .unlock-body-con .unlock-locking-tip-con { 130 | width: 100px; 131 | height: 30px; 132 | text-align: center; 133 | position: absolute; 134 | left: 50%; 135 | margin-left: -50px; 136 | bottom: -80px; 137 | color: white; 138 | font-size: 18px; 139 | } 140 | @keyframes unlock-in { 141 | 0% { 142 | transform: scale(0); 143 | } 144 | 80% { 145 | transform: scale(0); 146 | } 147 | 88% { 148 | transform: scale(1.3); 149 | } 150 | 100% { 151 | transform: scale(1); 152 | } 153 | } 154 | @keyframes unlock-out { 155 | 0% { 156 | transform: scale(1); 157 | } 158 | 60% { 159 | transform: scale(1.2); 160 | } 161 | 100% { 162 | transform: scale(0); 163 | } 164 | } 165 | .show-unlock-enter-active { 166 | animation: unlock-in 1.4s ease; 167 | } 168 | .show-unlock-leave-to { 169 | opacity: 0; 170 | } 171 | .show-unlock-leave-active { 172 | transition: opacity 0.2s; 173 | } 174 | -------------------------------------------------------------------------------- /webpage/src/main_components/unlock.less: -------------------------------------------------------------------------------- 1 | .unlock-body-con { 2 | position: absolute; 3 | width: 400px; 4 | height: 100px; 5 | left: 50%; 6 | top: 50%; 7 | margin-left: -200px; 8 | margin-top: -200px; 9 | transform-origin: center center; 10 | z-index: 10; 11 | 12 | .unlock-avator-con { 13 | position: absolute; 14 | left: 50%; 15 | top: 50%; 16 | transform: translate(-50%,-50%); 17 | box-sizing: border-box; 18 | width: 100px; 19 | height: 100px; 20 | border-radius: 50%; 21 | overflow: hidden; 22 | border: 2px solid white; 23 | cursor: pointer; 24 | transition: all 0.5s; 25 | z-index: 12; 26 | box-shadow: 0 0 10px 2px rgba(255, 255, 255, .3); 27 | 28 | .unlock-avator-img { 29 | width: 100%; 30 | height: 100%; 31 | display: block; 32 | z-index: 7; 33 | } 34 | 35 | .unlock-avator-cover { 36 | width: 100%; 37 | height: 100%; 38 | background: rgba(0, 0, 0, .6); 39 | z-index: 11600; 40 | position: absolute; 41 | left: 0; 42 | top: 0; 43 | opacity: 0; 44 | transition: opacity 0.2s; 45 | color: white; 46 | 47 | span { 48 | display: block; 49 | margin: 20px auto 5px; 50 | text-align: center; 51 | } 52 | 53 | p { 54 | text-align: center; 55 | font-size: 16px; 56 | font-weight: 500; 57 | } 58 | } 59 | 60 | &:hover .unlock-avator-cover { 61 | opacity: 1; 62 | transition: opacity 0.2s; 63 | } 64 | } 65 | 66 | .unlock-avator-under-back { 67 | position: absolute; 68 | left: 50%; 69 | top: 50%; 70 | transform: translate(-45px,-50%); 71 | box-sizing: border-box; 72 | width: 100px; 73 | height: 100px; 74 | border-radius: 50%; 75 | background: #667aa6; 76 | transition: all 0.5s; 77 | z-index: 5; 78 | } 79 | 80 | .unlock-input-con { 81 | position: absolute; 82 | height: 70px; 83 | width: 350px; 84 | top: 15px; 85 | right: 0; 86 | 87 | .unlock-input-overflow-con { 88 | position: absolute; 89 | width: 100%; 90 | height: 100%; 91 | left: 0; 92 | top: 0; 93 | overflow: hidden; 94 | 95 | .unlock-overflow-body { 96 | position: absolute; 97 | top: 0; 98 | right: 0; 99 | width: 100%; 100 | height: 100%; 101 | transition: all 0.5s ease 0.5s; 102 | 103 | .unlock-input { 104 | float: left; 105 | display: block; 106 | box-sizing: content-box; 107 | height: 22px; 108 | width: 230px; 109 | font-size: 18px; 110 | outline: none; 111 | padding: 11px 10px 11px 30px; 112 | border: 2px solid #e2ddde; 113 | margin-top: 10px; 114 | } 115 | 116 | .unlock-btn { 117 | float: left; 118 | display: block; 119 | font-size: 20px; 120 | padding: 7px 30px; 121 | cursor: pointer; 122 | border-radius: 0 25px 25px 0; 123 | border: 2px solid #e2ddde; 124 | border-left: none; 125 | background: #2d8cf0; 126 | outline: none; 127 | transition: all 0.2s; 128 | margin-top: 10px; 129 | 130 | &:hover { 131 | background: #5cadff; 132 | box-shadow: 0 0 10px 3px rgba(255, 255, 255, .2); 133 | } 134 | } 135 | 136 | .click-unlock-btn { 137 | background: #2b85e4 !important; 138 | } 139 | } 140 | } 141 | } 142 | 143 | .unlock-locking-tip-con { 144 | width: 100px; 145 | height: 30px; 146 | text-align: center; 147 | position: absolute; 148 | left: 50%; 149 | margin-left: -50px; 150 | bottom: -80px; 151 | color: white; 152 | font-size: 18px; 153 | } 154 | } 155 | @keyframes unlock-in { 156 | 0% { 157 | transform: scale(0); 158 | } 159 | 160 | 80% { 161 | transform: scale(0); 162 | } 163 | 164 | 88% { 165 | transform: scale(1.3); 166 | } 167 | 168 | 100% { 169 | transform: scale(1); 170 | } 171 | } 172 | @keyframes unlock-out { 173 | 0% { 174 | transform: scale(1); 175 | } 176 | 177 | 60% { 178 | transform: scale(1.2); 179 | } 180 | 181 | 100% { 182 | transform: scale(0); 183 | } 184 | } 185 | 186 | .show-unlock-enter-active { 187 | animation: unlock-in 1.4s ease; 188 | } 189 | 190 | .show-unlock-leave-to { 191 | opacity: 0; 192 | } 193 | 194 | .show-unlock-leave-active { 195 | transition: opacity 0.2s; 196 | } 197 | -------------------------------------------------------------------------------- /webpage/src/main_components/unlock.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 31 | 32 | 83 | -------------------------------------------------------------------------------- /webpage/src/styles/common.css: -------------------------------------------------------------------------------- 1 | .demo-spin-icon-load { 2 | animation: ani-demo-spin 1s linear infinite; 3 | } 4 | .margin-top-8 { 5 | margin-top: 8px; 6 | } 7 | .margin-top-10 { 8 | margin-top: 10px; 9 | } 10 | .margin-top-20 { 11 | margin-top: 20px; 12 | } 13 | .margin-left-10 { 14 | margin-left: 10px; 15 | } 16 | .margin-bottom-10 { 17 | margin-bottom: 10px; 18 | } 19 | .margin-bottom-100 { 20 | margin-bottom: 100px; 21 | } 22 | .margin-right-10 { 23 | margin-right: 10px; 24 | } 25 | .padding-left-6 { 26 | padding-left: 6px; 27 | } 28 | .padding-left-8 { 29 | padding-left: 5px; 30 | } 31 | .padding-left-10 { 32 | padding-left: 10px; 33 | } 34 | .padding-left-20 { 35 | padding-left: 20px; 36 | } 37 | .height-100 { 38 | height: 100%; 39 | } 40 | .height-120px { 41 | height: 100px; 42 | } 43 | .height-200px { 44 | height: 200px; 45 | } 46 | .height-492px { 47 | height: 492px; 48 | } 49 | .height-460px { 50 | height: 460px; 51 | } 52 | .line-gray { 53 | height: 0; 54 | border-bottom: 2px solid #dcdcdc; 55 | } 56 | .notwrap { 57 | word-break: keep-all; 58 | white-space: nowrap; 59 | overflow: hidden; 60 | text-overflow: ellipsis; 61 | } 62 | .padding-left-5 { 63 | padding-left: 10px; 64 | } 65 | [v-cloak] { 66 | display: none; 67 | } 68 | -------------------------------------------------------------------------------- /webpage/src/styles/common.less: -------------------------------------------------------------------------------- 1 | .demo-spin-icon-load{ 2 | animation: ani-demo-spin 1s linear infinite; 3 | } 4 | 5 | .margin-top-8 { 6 | margin-top: 8px; 7 | } 8 | 9 | .margin-top-10 { 10 | margin-top: 10px; 11 | } 12 | 13 | .margin-top-20 { 14 | margin-top: 20px; 15 | } 16 | 17 | .margin-left-10 { 18 | margin-left: 10px; 19 | } 20 | 21 | .margin-bottom-10 { 22 | margin-bottom: 10px; 23 | } 24 | 25 | .margin-bottom-100 { 26 | margin-bottom: 100px; 27 | } 28 | 29 | .margin-right-10 { 30 | margin-right: 10px; 31 | } 32 | 33 | .padding-left-6 { 34 | padding-left: 6px; 35 | } 36 | 37 | .padding-left-8 { 38 | padding-left: 5px; 39 | } 40 | 41 | .padding-left-10 { 42 | padding-left: 10px; 43 | } 44 | 45 | .padding-left-20 { 46 | padding-left: 20px; 47 | } 48 | 49 | .height-100 { 50 | height: 100%; 51 | } 52 | 53 | .height-120px { 54 | height: 100px; 55 | } 56 | 57 | .height-200px { 58 | height: 200px; 59 | } 60 | 61 | .height-492px { 62 | height: 492px; 63 | } 64 | 65 | .height-460px { 66 | height: 460px; 67 | } 68 | 69 | .line-gray { 70 | height: 0; 71 | border-bottom: 2px solid #dcdcdc; 72 | } 73 | 74 | .notwrap { 75 | word-break: keep-all; 76 | white-space: nowrap; 77 | overflow: hidden; 78 | text-overflow: ellipsis; 79 | } 80 | 81 | .padding-left-5 { 82 | padding-left: 10px; 83 | } 84 | 85 | [v-cloak] { 86 | display: none; 87 | } 88 | -------------------------------------------------------------------------------- /webpage/src/subnet.vue: -------------------------------------------------------------------------------- 1 | 4 | 9 | -------------------------------------------------------------------------------- /webpage/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/webpage/static/.gitkeep -------------------------------------------------------------------------------- /webpage/static/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/webpage/static/avatar.png -------------------------------------------------------------------------------- /webpage/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cookieY/Yearning-python/83bee3e0eff1b222eada442c447d68dccd0b45ea/webpage/static/icon.png -------------------------------------------------------------------------------- /webpage/static/particlesjs-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "particles": { 3 | "number": { 4 | "value": 20, 5 | "density": { 6 | "enable": true, 7 | "value_area": 800 8 | } 9 | }, 10 | "color": { 11 | "value": "#dcdcdc" 12 | }, 13 | "shape": { 14 | "type": "circle", 15 | "stroke": { 16 | "width": 0, 17 | "color": "#000000" 18 | }, 19 | "polygon": { 20 | "nb_sides": 10 21 | }, 22 | "image": { 23 | "src": "img/github.svg", 24 | "width": 100, 25 | "height": 100 26 | } 27 | }, 28 | "opacity": { 29 | "value": 0.809723090737089, 30 | "random": false, 31 | "anim": { 32 | "enable": false, 33 | "speed": 1, 34 | "opacity_min": 0.1, 35 | "sync": false 36 | } 37 | }, 38 | "size": { 39 | "value": 5, 40 | "random": false, 41 | "anim": { 42 | "enable": false, 43 | "speed": 22, 44 | "size_min": 0.1, 45 | "sync": false 46 | } 47 | }, 48 | "line_linked": { 49 | "enable": true, 50 | "distance": 150, 51 | "color": "#726b6b", 52 | "opacity": 0.4, 53 | "width": 1 54 | }, 55 | "move": { 56 | "enable": true, 57 | "speed": 6, 58 | "direction": "none", 59 | "random": false, 60 | "straight": false, 61 | "out_mode": "out", 62 | "bounce": false, 63 | "attract": { 64 | "enable": false, 65 | "rotateX": 600, 66 | "rotateY": 1200 67 | } 68 | } 69 | }, 70 | "interactivity": { 71 | "detect_on": "canvas", 72 | "events": { 73 | "onhover": { 74 | "enable": false, 75 | "mode": "grab" 76 | }, 77 | "onclick": { 78 | "enable": false, 79 | "mode": "repulse" 80 | }, 81 | "resize": true 82 | }, 83 | "modes": { 84 | "grab": { 85 | "distance": 400, 86 | "line_linked": { 87 | "opacity": 1 88 | } 89 | }, 90 | "bubble": { 91 | "distance": 400, 92 | "size": 40, 93 | "duration": 2, 94 | "opacity": 8, 95 | "speed": 3 96 | }, 97 | "repulse": { 98 | "distance": 200, 99 | "duration": 0.4 100 | }, 101 | "push": { 102 | "particles_nb": 4 103 | }, 104 | "remove": { 105 | "particles_nb": 2 106 | } 107 | } 108 | }, 109 | "retina_detect": true 110 | } 111 | -------------------------------------------------------------------------------- /webpage/test/e2e/custom-assertions/elementCount.js: -------------------------------------------------------------------------------- 1 | // A custom Nightwatch assertion. 2 | // the name of the method is the filename. 3 | // can be used in tests like this: 4 | // 5 | // browser.assert.elementCount(selector, count) 6 | // 7 | // for how to write custom assertions see 8 | // http://nightwatchjs.org/guide#writing-custom-assertions 9 | exports.assertion = function (selector, count) { 10 | this.message = 'Testing if element <' + selector + '> has count: ' + count 11 | this.expected = count 12 | this.pass = function (val) { 13 | return val === this.expected 14 | } 15 | this.value = function (res) { 16 | return res.value 17 | } 18 | this.command = function (cb) { 19 | var self = this 20 | return this.api.execute(function (selector) { 21 | return document.querySelectorAll(selector).length 22 | }, [selector], function (res) { 23 | cb.call(self, res) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webpage/test/e2e/nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | var config = require('../../config') 3 | 4 | // http://nightwatchjs.org/gettingstarted#settings-file 5 | module.exports = { 6 | src_folders: ['test/e2e/specs'], 7 | output_folder: 'test/e2e/reports', 8 | custom_assertions_path: ['test/e2e/custom-assertions'], 9 | 10 | selenium: { 11 | start_process: true, 12 | server_path: require('selenium-server').path, 13 | host: '127.0.0.1', 14 | port: 4444, 15 | cli_args: { 16 | 'webdriver.chrome.driver': require('chromedriver').path 17 | } 18 | }, 19 | 20 | test_settings: { 21 | default: { 22 | selenium_port: 4444, 23 | selenium_host: 'localhost', 24 | silent: true, 25 | globals: { 26 | devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port) 27 | } 28 | }, 29 | 30 | chrome: { 31 | desiredCapabilities: { 32 | browserName: 'chrome', 33 | javascriptEnabled: true, 34 | acceptSslCerts: true 35 | } 36 | }, 37 | 38 | firefox: { 39 | desiredCapabilities: { 40 | browserName: 'firefox', 41 | javascriptEnabled: true, 42 | acceptSslCerts: true 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /webpage/test/e2e/runner.js: -------------------------------------------------------------------------------- 1 | // 1. start the dev server using production config 2 | process.env.NODE_ENV = 'testing' 3 | var server = require('../../build/dev-server.js') 4 | 5 | server.ready.then(() => { 6 | // 2. run the nightwatch test suite against it 7 | // to run in additional browsers: 8 | // 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings" 9 | // 2. add it to the --env flag below 10 | // or override the environment flag, for example: `npm run e2e -- --env chrome,firefox` 11 | // For more information on Nightwatch's config file, see 12 | // http://nightwatchjs.org/guide#settings-file 13 | var opts = process.argv.slice(2) 14 | if (opts.indexOf('--config') === -1) { 15 | opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js']) 16 | } 17 | if (opts.indexOf('--env') === -1) { 18 | opts = opts.concat(['--env', 'chrome']) 19 | } 20 | 21 | var spawn = require('cross-spawn') 22 | var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' }) 23 | 24 | runner.on('exit', function (code) { 25 | server.close() 26 | process.exit(code) 27 | }) 28 | 29 | runner.on('error', function (err) { 30 | server.close() 31 | throw err 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /webpage/test/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // For authoring Nightwatch tests, see 2 | // http://nightwatchjs.org/guide#usage 3 | 4 | module.exports = { 5 | 'default e2e tests': function (browser) { 6 | // automatically uses dev Server port from /config.index.js 7 | // default: http://localhost:8080 8 | // see nightwatch.conf.js 9 | const devServer = browser.globals.devServerURL 10 | 11 | browser 12 | .url(devServer) 13 | .waitForElementVisible('#app', 5000) 14 | .assert.elementPresent('.hello') 15 | .assert.containsText('h1', 'Welcome to Your Vue.js App') 16 | .assert.elementCount('img', 1) 17 | .end() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /webpage/test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "expect": true, 7 | "sinon": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /webpage/test/unit/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | Vue.config.productionTip = false 4 | 5 | // require all test files (files that ends with .spec.js) 6 | const testsContext = require.context('./specs', true, /\.spec$/) 7 | testsContext.keys().forEach(testsContext) 8 | 9 | // require all src files except main.js for coverage. 10 | // you can also change this to match only the subset of files that 11 | // you want coverage for. 12 | const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/) 13 | srcContext.keys().forEach(srcContext) 14 | -------------------------------------------------------------------------------- /webpage/test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | // This is a karma config file. For more details see 2 | // http://karma-runner.github.io/0.13/config/configuration-file.html 3 | // we are also using it with karma-webpack 4 | // https://github.com/webpack/karma-webpack 5 | 6 | var webpackConfig = require('../../build/webpack.test.conf') 7 | 8 | module.exports = function (config) { 9 | config.set({ 10 | // to run in additional browsers: 11 | // 1. install corresponding karma launcher 12 | // http://karma-runner.github.io/0.13/config/browsers.html 13 | // 2. add it to the `browsers` array below. 14 | browsers: ['PhantomJS'], 15 | frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'], 16 | reporters: ['spec', 'coverage'], 17 | files: ['./index.js'], 18 | preprocessors: { 19 | './index.js': ['webpack', 'sourcemap'] 20 | }, 21 | webpack: webpackConfig, 22 | webpackMiddleware: { 23 | noInfo: true 24 | }, 25 | coverageReporter: { 26 | dir: './coverage', 27 | reporters: [ 28 | { type: 'lcov', subdir: '.' }, 29 | { type: 'text-summary' } 30 | ] 31 | } 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /webpage/test/unit/specs/Hello.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Hello from '@/components/Hello' 3 | 4 | describe('Hello.vue', () => { 5 | it('should render correct contents', () => { 6 | const Constructor = Vue.extend(Hello) 7 | const vm = new Constructor().$mount() 8 | expect(vm.$el.querySelector('.hello h1').textContent) 9 | .to.equal('Welcome to Your Vue.js App') 10 | }) 11 | }) 12 | --------------------------------------------------------------------------------