├── .gitignore ├── LICENSE ├── README.md ├── Sparrow ├── __init__.py ├── _const.py ├── action │ ├── account_action.py │ ├── action_action.py │ ├── api_action.py │ ├── common_action.py │ ├── project_action.py │ ├── res_template_action.py │ ├── response.py │ └── track.py ├── forms.py ├── settings.py ├── urls.py └── wsgi.py ├── dal ├── __init__.py ├── admin.py ├── apps.py ├── dao │ ├── account_dao.py │ ├── action_dao.py │ ├── api_dao.py │ ├── project_dao.py │ ├── quick_login_record_dao.py │ └── res_template_dao.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_action.py │ ├── 0003_auto_20180516_0736.py │ ├── 0004_quickloginrecord.py │ ├── 0005_quickloginrecord_verification_code.py │ ├── 0006_auto_20180517_0815.py │ ├── 0007_quickloginrecord_update_time.py │ ├── 0008_restemplate_type.py │ ├── 0009_auto_20180615_0743.py │ ├── 0010_auto_20180615_0806.py │ ├── 0011_auto_20180615_0916.py │ ├── 0012_api_responsejson.py │ ├── 0013_auto_20180615_1011.py │ ├── 0014_auto_20180615_1012.py │ ├── 0015_auto_20180615_1015.py │ ├── 0016_remove_api_responsejson.py │ └── __init__.py ├── models.py ├── tests.py └── views.py ├── frontend ├── .appveyor.yml.swp ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── appveyor.yml ├── backers.md ├── 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 ├── client │ ├── App.vue │ ├── app.js │ ├── assets │ │ ├── logo.png │ │ ├── logo.svg │ │ └── logo@2x.png │ ├── components │ │ └── layout │ │ │ ├── AppMain.vue │ │ │ ├── FooterBar.vue │ │ │ ├── Levelbar.vue │ │ │ ├── Navbar.vue │ │ │ ├── Sidebar.vue │ │ │ └── index.js │ ├── filters │ │ └── index.js │ ├── index.js │ ├── router │ │ └── index.js │ ├── store │ │ ├── actions.js │ │ ├── getters.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── app.js │ │ │ └── menu │ │ │ │ ├── index.js │ │ │ │ └── lazyLoading.js │ │ └── mutation-types.js │ └── views │ │ ├── 403.vue │ │ ├── Home.vue │ │ ├── api │ │ ├── create.vue │ │ ├── detail.vue │ │ └── update.vue │ │ ├── auth │ │ └── Login.vue │ │ ├── axios │ │ └── index.vue │ │ ├── components │ │ ├── APIList.vue │ │ ├── JsonEditor.vue │ │ ├── Modal.vue │ │ ├── PopupView.vue │ │ ├── ProjectChooser.vue │ │ ├── TemplateChooser.vue │ │ └── modals │ │ │ ├── CardModal.vue │ │ │ ├── ImageModal.vue │ │ │ └── Modal.vue │ │ ├── dashboard │ │ └── index.vue │ │ ├── favorite │ │ └── index.vue │ │ ├── network.js │ │ ├── notification.js │ │ ├── project │ │ ├── ProjectSetting.vue │ │ ├── create.vue │ │ ├── detail.vue │ │ └── index.vue │ │ └── template │ │ ├── create.vue │ │ ├── detail.vue │ │ ├── index.vue │ │ └── update.vue ├── config │ ├── dev.env.js │ ├── index.js │ ├── prod.env.js │ └── test.env.js ├── dist │ ├── assets │ │ ├── css │ │ │ ├── app.b108b2ac131ab52ab4a1b7ccd1a3c1f8.css │ │ │ └── app.b108b2ac131ab52ab4a1b7ccd1a3c1f8.css.map │ │ ├── fonts │ │ │ ├── element-icons.6f0a763.ttf │ │ │ ├── fontawesome-webfont.674f50d.eot │ │ │ ├── fontawesome-webfont.af7ae50.woff2 │ │ │ ├── fontawesome-webfont.b06871f.ttf │ │ │ └── fontawesome-webfont.fee66e7.woff │ │ ├── img │ │ │ ├── fontawesome-webfont.912ec66.svg │ │ │ └── jsoneditor-icons.bfab7b1.svg │ │ └── js │ │ │ ├── 0.6790fcd134bf424cd12a.js │ │ │ ├── 0.6790fcd134bf424cd12a.js.map │ │ │ ├── 1.4d861863b18b4852603e.js │ │ │ ├── 1.4d861863b18b4852603e.js.map │ │ │ ├── 2.eb824f5d42732f866eb7.js │ │ │ ├── 2.eb824f5d42732f866eb7.js.map │ │ │ ├── 3.4681664356e6c17aae8b.js │ │ │ ├── 3.4681664356e6c17aae8b.js.map │ │ │ ├── 4.23fab30cde5ebb4b6a96.js │ │ │ ├── 4.23fab30cde5ebb4b6a96.js.map │ │ │ ├── 5.c760f725e308a0951f63.js │ │ │ ├── 5.c760f725e308a0951f63.js.map │ │ │ ├── 6.385af40d5503f5dbcdd6.js │ │ │ ├── 6.385af40d5503f5dbcdd6.js.map │ │ │ ├── app.c16afd787002409a0e86.js │ │ │ ├── app.c16afd787002409a0e86.js.map │ │ │ ├── manifest.228aafaf19fdc45611fe.js │ │ │ ├── manifest.228aafaf19fdc45611fe.js.map │ │ │ ├── vendor.55811f790bc18a030949.js │ │ │ └── vendor.55811f790bc18a030949.js.map │ ├── index.html │ └── logo.png ├── electronIndex.js ├── index.html ├── package-lock.json ├── package.json └── yarn.lock ├── log └── .gitkeep ├── manage.py ├── requirements.txt └── res ├── Banner.png ├── data-01.png ├── data-03.png └── data-04.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # pycharm 7 | .idea 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | # build/ 16 | develop-eggs/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | *.log.* 58 | local_settings.py 59 | *.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | .venv 91 | venv/ 92 | ENV/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 周小鱼 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sparrow 2 | 3 | ![Banner](res/Banner.png) 4 | 5 | ## 功能简介 6 | 7 | Sparrow 是为解决**移动端开发**的痛点而开发的 Mock 系统,Sever 配合移动端 SDK,实现无耦合、无代码修改的 Mock 功能,极大程度的提升 Mock 的体验感、开发效率、测试效率。 8 | 9 | 关于移动端 SDK,请访问 Sparrow SDK([SparrowSDK-iOS](https://github.com/eleme/SparrowSDK-iOS)、[Sparrow-Android]( https://github.com/eleme/SparrowSDK-Android))。 10 | 11 | ### 示例 12 | 13 | 设置一个 Mock API,用于骑手 App『订单申请报备异常原因列表』: 14 | 15 | ![](res/data-04.png) 16 | 17 | ![](res/data-03.png) 18 | 19 | 如上图所示,Sparrow 可以实现不修改网络请求的代码,获取数据,并保留整个网络请求的过程,不需要手机设置代理。 20 | 21 | ## 服务器部署 22 | 23 | ### 基础环境 24 | 25 | 1. Python3 26 | 2. pip3 27 | 28 | ### install 29 | 30 | ```shell 31 | git clone git@git.elenet.me:LPD-iOS/Sparrow.git 32 | cd Sparrow 33 | pip install -r requirements.txt 34 | python3 manage.py makemigrations 35 | python3 manage.py migrate 36 | python3 manage.py runserver 0.0.0.0:80 37 | ``` 38 | 39 | ### 自定义前端 40 | 41 | **如果需要自行修改前端内容,还需要以下环境:** 42 | 43 | 1. npm 基本环境 44 | 2. vue 环境 45 | 46 | 47 | ```shell 48 | npm run build 49 | ``` 50 | 51 | > 具体还请参考 [Vue 的官方文档](https://cn.vuejs.org/index.html) 52 | 53 | 54 | ### 数据库配置 55 | 56 | 默认配置: 57 | 58 | 1. USER: test 59 | 2. PASSWORD: 123abc 60 | 3. HOST: 127.0.0.1 61 | 4. PORT: 3306 62 | 63 | 也可以在以下位置自行修改: 64 | 65 | Sparrow -> settings.py -> 66 | 67 | ### 创建 Admin 超级管理员 68 | 69 | ``` 70 | python3 manage.py createsuperuser 71 | ``` 72 | 73 | 示例过程 74 | 75 | ``` 76 | python3 manage.py createsuperuser 77 | Username (leave blank to use 'zhoulingyu'): lingyu.zhou 78 | Email address: lingyu.zhou@ele.me 79 | Password: *** 80 | Password (again): *** 81 | Superuser created successfully. 82 | ``` 83 | 84 | ### 后台登录地址 85 | 86 | 完整域名/admin 87 | 88 | 可以通过登录 admin 修改个人信息 89 | 90 | 91 | -------------------------------------------------------------------------------- /Sparrow/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/Sparrow/__init__.py -------------------------------------------------------------------------------- /Sparrow/_const.py: -------------------------------------------------------------------------------- 1 | class Const(object): 2 | class ConstError(PermissionError): 3 | pass 4 | 5 | class ConstCaseError(ConstError): 6 | pass 7 | 8 | def __setattr__(self, name, value): 9 | if name in self.__dict__.keys(): 10 | raise self.ConstError("Can't rebind const(%s)" % name) 11 | self.__dict__[name] = value 12 | 13 | def __delattr__(self, name): 14 | if name in self.__dict__: 15 | raise self.ConstError("Can't unbind const(%s)" % name) 16 | raise NameError(name) 17 | 18 | 19 | import sys 20 | 21 | sys.modules[__name__] = Const() 22 | 23 | Const.kError = "kError" 24 | -------------------------------------------------------------------------------- /Sparrow/action/account_action.py: -------------------------------------------------------------------------------- 1 | from django.forms.models import model_to_dict 2 | from Sparrow.action.common_action import * 3 | from django.contrib import auth 4 | from dal.dao.account_dao import AccountDao 5 | from dal.dao.quick_login_record_dao import QuickLoginRecordDao 6 | from django.contrib.auth.decorators import login_required 7 | from Sparrow.action.response import * 8 | from django.http.request import * 9 | from Sparrow.action.track import track 10 | from dal.models import * 11 | import uuid 12 | from datetime import datetime, timezone 13 | 14 | 15 | class AccountAction: 16 | @csrf_exempt 17 | @track(ActionType.AccountLogin) 18 | def login(request: HttpRequest): 19 | # if request.user.is_authenticated():ss 20 | # data = CommonData.response_data(Success, 'Success') 21 | 22 | # body = bytes.decode(request.body) 23 | # loginData = json.loads(body) 24 | 25 | username = request.POST['username'] 26 | password = request.POST['password'] 27 | 28 | print('用户 ' + username + ' 尝试登录') 29 | 30 | user = auth.authenticate(username=username, password=password) 31 | 32 | if user is not None and user.is_active: 33 | auth.login(request, user) 34 | accountInfo = User.objects.get(id=user.id) 35 | response = Response(Success, 'Success', {'id': accountInfo.id, 36 | 'username': accountInfo.username, 37 | 'email': accountInfo.email}) 38 | return HttpResponse(response.toJson(), content_type='application/json') 39 | else: 40 | response = Response(LoginFailed, 'Login failed', {}) 41 | return HttpResponse(response.toJson(), content_type='application/json') 42 | 43 | @track(ActionType.AccountQuickLogin) 44 | def quick_login(request: HttpRequest): 45 | if request.method != CommonData.Method.GET.value: 46 | return HttpResponse(Response.methodInvalidResponse().toJson(), content_type='application/json') 47 | user_id = request.GET.get('user_id') 48 | verification_code = request.GET.get('verification_code') 49 | 50 | record = QuickLoginRecordDao.get_record_with_verification_code(verification_code) 51 | if record is None: 52 | response = Response(QuickLoginFailed, '验证码不存在或已过期', {}) 53 | return HttpResponse(response.toJson(), content_type='application/json') 54 | 55 | now = datetime.now(timezone.utc) 56 | offset = (now - record.update_time).seconds 57 | 58 | if offset > 60: 59 | response = Response(QuickLoginFailed, '验证码已过期', {}) 60 | return HttpResponse(response.toJson(), content_type='application/json') 61 | 62 | user = AccountDao.get_user_with_id(user_id) 63 | if user is None: 64 | response = Response(QuickLoginFailed, '用户不存在', {}) 65 | return HttpResponse(response.toJson(), content_type='application/json') 66 | user.backend = 'django.contrib.auth.backends.ModelBackend' 67 | print('用户 ' + user.username + ' 尝试登录') 68 | auth.login(request, user) 69 | accountInfo = User.objects.get(id=user.id) 70 | response = Response(Success, 'Success', {'id': accountInfo.id, 71 | 'username': accountInfo.username, 72 | 'email': accountInfo.email}) 73 | return HttpResponse(response.toJson(), content_type='application/json') 74 | 75 | @track(ActionType.AccountRequestQuickLogin) 76 | def request_quick_login(request: HttpRequest): 77 | if request.method != CommonData.Method.GET.value: 78 | return HttpResponse(Response.methodInvalidResponse().toJson(), content_type='application/json') 79 | user = request.user 80 | r = QuickLoginRecordDao.get_record_with_user_id(user.id) 81 | if r is not None: 82 | r.verification_code = str(uuid.uuid4()) 83 | QuickLoginRecordDao.update_record(r) 84 | response = Response(Success, 'Success', {'verification_code': r.verification_code}) 85 | return HttpResponse(response.toJson(), content_type='application/json') 86 | else: 87 | record = QuickLoginRecord() 88 | record.user = user 89 | record.verification_code = str(uuid.uuid4()) 90 | QuickLoginRecordDao.add_record(record) 91 | response = Response(Success, 'Success', {'verification_code': record.verification_code}) 92 | return HttpResponse(response.toJson(), content_type='application/json') 93 | 94 | 95 | @login_required 96 | @csrf_exempt 97 | @track(ActionType.AccountLogout) 98 | def logout(request: HttpRequest): 99 | auth.logout(request) 100 | response = Response(Success, 'Success', {}) 101 | return HttpResponse(response.toJson(), content_type='application/json') 102 | 103 | def redirect_login(request: HttpRequest): 104 | response = Response(NeedLogin, 'Need Login', {}) 105 | return HttpResponse(response.toJson(), content_type='application/json') 106 | 107 | @track(ActionType.AccountCheckStatus) 108 | @login_required 109 | def check_status(request: HttpRequest): 110 | response = Response(Success, 'Has logged', {}) 111 | return HttpResponse(response.toJson(), content_type='application/json') 112 | 113 | @track(ActionType.AccountSearch) 114 | def search(request: HttpRequest): 115 | if request.method != CommonData.Method.GET.value: 116 | response = Response(RequestMethodError, 'Method is invalid', {}) 117 | return HttpResponse(response.toJson(), content_type='application/json') 118 | 119 | users = AccountDao.get_users_with_username(request.GET['username']) 120 | response = Response(Success, 'Success', users) 121 | return HttpResponse(response.toJson(), content_type='application/json') 122 | -------------------------------------------------------------------------------- /Sparrow/action/action_action.py: -------------------------------------------------------------------------------- 1 | from django.forms.models import model_to_dict 2 | from Sparrow.action.common_action import * 3 | from dal.dao.action_dao import ActionDao 4 | from django.contrib.auth.decorators import login_required 5 | from Sparrow.action.response import * 6 | from django.http.request import * 7 | from Sparrow.action.track import track 8 | from dal.models import * 9 | 10 | 11 | class ActionAction: 12 | @track(ActionType.ActionDailyActiveInfo) 13 | def daily_active_info(request: HttpRequest): 14 | daily_active_info = ActionDao.get_daily_active_info(14) 15 | response = Response(Success, 'Success', daily_active_info) 16 | return HttpResponse(response.to_json_with_mm_dd(), content_type='application/json') 17 | 18 | @track(ActionType.ActionTopActiveUserInfo) 19 | def top_active_users_info(request: HttpRequest): 20 | top_active_users_info = ActionDao.get_top_active_users_info(10) 21 | response = Response(Success, 'Success', top_active_users_info) 22 | return HttpResponse(response.toJson(), content_type='application/json') 23 | 24 | @track(ActionType.ActionTopActiveApisInfo) 25 | def top_active_apis_info(request: HttpRequest): 26 | top_active_apis_info = ActionDao.get_top_active_apis_info(10) 27 | response = Response(Success, 'Success', top_active_apis_info) 28 | return HttpResponse(response.toJson(), content_type='application/json') -------------------------------------------------------------------------------- /Sparrow/action/common_action.py: -------------------------------------------------------------------------------- 1 | from django.views.decorators.csrf import csrf_exempt 2 | from dal.dao.api_dao import ApiDao 3 | import Sparrow._const 4 | from django.shortcuts import render 5 | from django.http import HttpResponse 6 | from Sparrow.action.response import * 7 | from Sparrow.action.track import track 8 | from dal.models import * 9 | import uuid 10 | from dal.dao.res_template_dao import ResTemplateDao 11 | 12 | 13 | def datetime2string(o): 14 | if isinstance(o, datetime.datetime): 15 | return o.__str__() 16 | 17 | 18 | class CommonData(object): 19 | def response_data(code, message): 20 | dic = {'code': code, 'message': message} 21 | return dic 22 | 23 | @unique 24 | class Method(Enum): 25 | GET = 'GET' 26 | POST = 'POST' 27 | 28 | 29 | def index(request): 30 | context = {} 31 | return render(request, 'index.html', context) 32 | 33 | 34 | @csrf_exempt 35 | @track(ActionType.Mock) 36 | def mock(request, project_id, path): 37 | method = str(request.method) 38 | api = ApiDao.get_api(project_id, path, method) 39 | 40 | if api is None: 41 | response = Response(APINotExist, '该 Method 的 API 不存在', {}) 42 | return HttpResponse(response.toJson(), content_type='application/json') 43 | if api.status != Api.Status.Mock.value: 44 | response = Response(APINotOpenMock, '该 API 没有开启 Mock', {}) 45 | return HttpResponse(response.toJson(), content_type='application/json') 46 | 47 | data = json.loads(api.resTemplate.responseJson) 48 | return HttpResponse(json.dumps(data), content_type='application/json') 49 | 50 | 51 | def move(request): 52 | apis = ApiDao.get_all_api_list() 53 | response = Response(Success, 54 | 'Success', 55 | list(apis)) 56 | for api in apis: 57 | res = ResTemplate() 58 | res.name = uuid.uuid4() 59 | res.mimeType = 0 60 | res.responseJson = api['responseJson'] 61 | res.type = ResTemplate.Type.BelongsToProject.value 62 | 63 | project = Project.objects.get(project_id=int(api['project'])) 64 | 65 | project.save() 66 | res.project = project 67 | result = ResTemplateDao.create(res) 68 | a = ApiDao.get_api_with_id(api['api_id']) 69 | a.resTemplate = result 70 | a.save() 71 | return HttpResponse(response.toJson(), content_type='application/json') 72 | -------------------------------------------------------------------------------- /Sparrow/action/response.py: -------------------------------------------------------------------------------- 1 | import json 2 | import datetime 3 | 4 | 5 | def datetime2string(o): 6 | if isinstance(o, datetime.datetime): 7 | return o.__str__() 8 | 9 | 10 | def datetime2string_mm_dd(o): 11 | if isinstance(o, datetime.datetime): 12 | return o.strftime('%m-%d') 13 | 14 | 15 | Success = 200 16 | LoginFailed = 900 17 | NeedLogin = 901 18 | QuickLoginFailed = 902 19 | 20 | APINotExist = 1000 21 | FormParseError = 1001 22 | DaoOperationError = 1002 23 | RequestMethodError = 1003 24 | RequestParamsError = 1004 25 | MissingParametersError = 1005 26 | APINotOpenMock = 1006 27 | APIAlreadyExist = 1007 28 | 29 | 30 | class Response(object): 31 | def __init__(self, code, message, data): 32 | self.code = code 33 | self.message = message 34 | self.data = data 35 | 36 | def toJson(self): 37 | return json.dumps(self.__dict__, default=datetime2string, ensure_ascii=False) 38 | 39 | def to_json_with_mm_dd(self): 40 | return json.dumps(self.__dict__, default=datetime2string_mm_dd) 41 | 42 | @staticmethod 43 | def methodInvalidResponse(): 44 | response = Response(RequestMethodError, 'Method is invalid', {}) 45 | return response 46 | 47 | @staticmethod 48 | def successResponse(): 49 | response = Response(Success, 'Success', {}) 50 | return response 51 | 52 | @staticmethod 53 | def daoOperationErrorResponse(): 54 | response = Response(DaoOperationError, 'DaoOperation error', {}) 55 | return response 56 | 57 | @staticmethod 58 | def requestParamsErrorResponse(): 59 | response = Response(RequestParamsError, 'RequestParams error', {}) 60 | return response 61 | 62 | @staticmethod 63 | def formParseErrorResponse(): 64 | response = Response(FormParseError, 'FormParse error', {}) 65 | return response 66 | -------------------------------------------------------------------------------- /Sparrow/action/track.py: -------------------------------------------------------------------------------- 1 | from dal.models import * 2 | from dal.dao.action_dao import ActionDao 3 | import types 4 | from django.http.request import * 5 | 6 | 7 | def track(type): 8 | def decorator(func): 9 | def wrapper(*args, **kw): 10 | action = Action() 11 | action.type = type.value 12 | action.function_info = func.__name__ 13 | action.request_info = args[0].__str__() 14 | 15 | if args[0].user.id is not None : 16 | action.user = args[0].user 17 | ActionDao.add_action(action) 18 | return func(*args, **kw) 19 | 20 | return wrapper 21 | 22 | return decorator 23 | -------------------------------------------------------------------------------- /Sparrow/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | 3 | class ApiCreateForm(forms.Form): 4 | path = forms.CharField(max_length=128) 5 | method = forms.CharField(max_length=8) 6 | name = forms.CharField(max_length=128) 7 | note = forms.CharField(max_length=512, required=False) 8 | status = forms.IntegerField() 9 | responseJson = forms.CharField(widget=forms.Textarea) 10 | 11 | class ApiUpdateForm(forms.Form): 12 | path = forms.CharField(max_length=128) 13 | method = forms.CharField(max_length=8) 14 | name = forms.CharField(max_length=128) 15 | note = forms.CharField(max_length=512, required=False) 16 | status = forms.IntegerField() 17 | responseJson = forms.CharField(widget=forms.Textarea) 18 | 19 | class ProjectCreateForm(forms.Form): 20 | name = forms.CharField(max_length=128) 21 | note = forms.CharField(max_length=512, required=False) 22 | permissionType = forms.IntegerField() 23 | status = forms.IntegerField() 24 | 25 | class ProjectUpateForm(forms.Form): 26 | name = forms.CharField(max_length=128) 27 | note = forms.CharField(max_length=512, required=False) 28 | permissionType = forms.IntegerField() 29 | status = forms.IntegerField() 30 | 31 | class ResTemplateCreateForm(forms.Form): 32 | name = forms.CharField(max_length=128) 33 | note = forms.CharField(max_length=512, required=False) 34 | type = forms.IntegerField() 35 | mimeType = forms.IntegerField() 36 | responseJson = forms.CharField(widget=forms.Textarea()) 37 | 38 | class ResTemplateUpdateForm(forms.Form): 39 | name = forms.CharField(max_length=128) 40 | note = forms.CharField(max_length=512, required=False) 41 | type = forms.IntegerField() 42 | mimeType = forms.IntegerField() 43 | responseJson = forms.CharField(widget=forms.Textarea()) -------------------------------------------------------------------------------- /Sparrow/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for SparrowAdmin project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | # Quick-start development settings - unsuitable for production 19 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 20 | 21 | # SECURITY WARNING: keep the secret key used in production secret! 22 | SECRET_KEY = '!_fo9@k!wur6zxx72a24g10*w+_y6&!i229%cnki!4vcy%q76m' 23 | 24 | # SECURITY WARNING: don't run with debug turned on in production! 25 | DEBUG = True 26 | 27 | ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] 28 | 29 | # Application definition 30 | 31 | INSTALLED_APPS = [ 32 | 'django.contrib.admin', 33 | 'django.contrib.auth', 34 | 'django.contrib.contenttypes', 35 | 'django.contrib.sessions', 36 | 'django.contrib.messages', 37 | 'django.contrib.staticfiles', 38 | 'corsheaders', 39 | 'dal', 40 | 'guardian', 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.contrib.sessions.middleware.SessionMiddleware', 45 | 'corsheaders.middleware.CorsMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | 'django.middleware.security.SecurityMiddleware', 52 | ] 53 | 54 | AUTHENTICATION_BACKENDS = ( 55 | 'django.contrib.auth.backends.ModelBackend', # default 56 | 'guardian.backends.ObjectPermissionBackend', 57 | ) 58 | 59 | ROOT_URLCONF = 'Sparrow.urls' 60 | 61 | TEMPLATES = [ 62 | { 63 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 64 | 'DIRS': ["frontend/dist", ], 65 | # 'DIRS': [os.path.join(BASE_DIR, 'frontend/dist')], 66 | 'APP_DIRS': True, 67 | 'OPTIONS': { 68 | 'context_processors': [ 69 | 'django.template.context_processors.debug', 70 | 'django.template.context_processors.request', 71 | 'django.contrib.auth.context_processors.auth', 72 | 'django.contrib.messages.context_processors.messages', 73 | ], 74 | }, 75 | }, 76 | ] 77 | 78 | WSGI_APPLICATION = 'Sparrow.wsgi.application' 79 | 80 | # Database 81 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 82 | 83 | DATABASES = { 84 | 'default': { 85 | 'ENGINE': 'django.db.backends.sqlite3', 86 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 87 | 'USER': 'test', 88 | 'PASSWORD': '123abc', 89 | 'HOST': '127.0.0.1', 90 | 'PORT': '3306', 91 | } 92 | } 93 | 94 | # Password validation 95 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 96 | 97 | AUTH_PASSWORD_VALIDATORS = [ 98 | # { 99 | # # 用户属性相似验证 100 | # 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 101 | # }, 102 | { 103 | # 最小长度验证 104 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 105 | 'OPTIONS': { 106 | 'min_length': 4, 107 | } 108 | }, 109 | { 110 | # 常见密码验证,这个检查器会对比常用的弱密码,这些常用密码被 gzip 打包储存在 `django/contrib/auth/common-passwords.txt.gz` 中 111 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 112 | }, 113 | { 114 | # 纯数字密码验证 115 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 116 | }, 117 | ] 118 | 119 | LOGGING = { 120 | 'version': 1, 121 | 'disable_existing_loggers': True, 122 | 'formatters': { 123 | 'standard': { 124 | 'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'} 125 | # 日志格式 126 | }, 127 | 'filters': { 128 | }, 129 | 'handlers': { 130 | 'default': { 131 | 'level': 'DEBUG', 132 | 'class': 'logging.handlers.RotatingFileHandler', 133 | 'filename': 'log/all.log', # 日志输出文件 134 | 'maxBytes': 1024 * 1024 * 5, # 文件大小 135 | 'backupCount': 5, # 备份份数 136 | 'formatter': 'standard', 137 | }, 138 | 'error': { 139 | 'level': 'ERROR', 140 | 'class': 'logging.handlers.RotatingFileHandler', 141 | 'filename': 'log/error.log', 142 | 'maxBytes': 1024 * 1024 * 5, 143 | 'backupCount': 5, 144 | 'formatter': 'standard', 145 | }, 146 | 'console': { 147 | 'level': 'DEBUG', 148 | 'class': 'logging.StreamHandler', 149 | } 150 | }, 151 | 'loggers': { 152 | 'django': { 153 | 'handlers': ['default', 'console'], 154 | 'level': 'DEBUG', 155 | 'propagate': False 156 | } 157 | } 158 | } 159 | 160 | LOGIN_URL = '/frontend/account/redirect_login' 161 | 162 | # Internationalization 163 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 164 | 165 | LANGUAGE_CODE = 'en-us' 166 | 167 | TIME_ZONE = 'UTC' 168 | 169 | USE_I18N = True 170 | 171 | USE_L10N = True 172 | 173 | USE_TZ = True 174 | 175 | # Static files (CSS, JavaScript, Images) 176 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 177 | 178 | STATIC_URL = '/static/' 179 | 180 | STATICFILES_DIRS = ( 181 | os.path.join(BASE_DIR, "frontend/dist"), 182 | ) 183 | 184 | STATICFILES_FINDERS = ( 185 | "django.contrib.staticfiles.finders.FileSystemFinder", 186 | "django.contrib.staticfiles.finders.AppDirectoriesFinder" 187 | ) 188 | 189 | CORS_ORIGIN_ALLOW_ALL = True 190 | -------------------------------------------------------------------------------- /Sparrow/urls.py: -------------------------------------------------------------------------------- 1 | """SparrowAdmin URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | from django.contrib import admin 17 | from django.urls import path 18 | from Sparrow import action 19 | from Sparrow.action.api_action import ApiAction 20 | from Sparrow.action.project_action import ProjectAction 21 | from Sparrow.action.res_template_action import ResTemplateAction 22 | from Sparrow.action.account_action import AccountAction 23 | from Sparrow.action.action_action import ActionAction 24 | 25 | urlpatterns = [ 26 | path('admin/', admin.site.urls), 27 | 28 | path('', action.common_action.index, name='index'), 29 | 30 | path('mock//', action.common_action.mock), 31 | 32 | path('frontend/project/list', ProjectAction.list), 33 | path('frontend/project/detail/', ProjectAction.detail), 34 | path('frontend/project/create', ProjectAction.create), 35 | path('frontend/project/update/', ProjectAction.update), 36 | path('frontend/project/repeat_name_verification', ProjectAction.repeat_name_verification), 37 | path('frontend/project/delete', ProjectAction.delete), 38 | path('frontend/project//members', ProjectAction.get_members), 39 | path('frontend/project//members/remove/', ProjectAction.remove_member), 40 | path('frontend/project//members/add', ProjectAction.add_member), 41 | 42 | path('frontend/api/fetch', ApiAction.fetch), 43 | path('frontend/api/batch_update_status', ApiAction.batch_update_status), 44 | path('frontend/api/copy/', ApiAction.copy), 45 | path('frontend/project//api/create', ApiAction.create), 46 | path('frontend/project//api/list', ApiAction.list), 47 | path('frontend/project//api/repeat_name_verification', ApiAction.repeat_name_verification), 48 | path('frontend/project//api/delete/', ApiAction.delete), 49 | path('frontend/project//api/detail/', ApiAction.detail), 50 | path('frontend/project//api/update/', ApiAction.update), 51 | path('frontend/project//api/star/', ApiAction.star), 52 | path('frontend/project//api//update_status', ApiAction.update_status), 53 | 54 | path('frontend/favorite/list', ApiAction.starList), 55 | 56 | path('frontend/res_template/list', ResTemplateAction.list), 57 | path('frontend/res_template/detail/', ResTemplateAction.detail), 58 | path('frontend/res_template/create', ResTemplateAction.create), 59 | path('frontend/res_template/repeat_name_verification', ResTemplateAction.repeat_name_verification), 60 | path('frontend/res_template/delete/', ResTemplateAction.delete), 61 | path('frontend/res_template/update/', ResTemplateAction.update), 62 | 63 | path('frontend/account/login', AccountAction.login), 64 | path('frontend/account/quick_login', AccountAction.quick_login), 65 | path('frontend/account/request_quick_login', AccountAction.request_quick_login), 66 | path('frontend/account/logout', AccountAction.logout), 67 | path('frontend/account/redirect_login', AccountAction.redirect_login), 68 | path('frontend/account/check_status', AccountAction.check_status), 69 | path('frontend/account/search', AccountAction.search), 70 | 71 | path('frontend/action/daily_active', ActionAction.daily_active_info), 72 | path('frontend/action/top_active_users_info', ActionAction.top_active_users_info), 73 | path('frontend/action/top_active_apis_info', ActionAction.top_active_apis_info), 74 | 75 | path('api/move', action.common_action.move), 76 | ] 77 | -------------------------------------------------------------------------------- /Sparrow/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for SparrowAdmin project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Sparrow.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /dal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/dal/__init__.py -------------------------------------------------------------------------------- /dal/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from dal.models import * 3 | 4 | # Register your models here. 5 | 6 | admin.site.register([Project, ResTemplate, Api]) -------------------------------------------------------------------------------- /dal/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class DaoConfig(AppConfig): 5 | name = 'dal' 6 | -------------------------------------------------------------------------------- /dal/dao/account_dao.py: -------------------------------------------------------------------------------- 1 | from dal.models import * 2 | import datetime 3 | from django.contrib.auth.models import User 4 | 5 | 6 | class AccountDao: 7 | @staticmethod 8 | def get_userProfile_with_id(id): 9 | userProfile = UserProfile.objects.get(user__id=id) 10 | return userProfile 11 | 12 | @staticmethod 13 | def get_user_with_id(id): 14 | try: 15 | user = User.objects.get(id=id) 16 | return user 17 | except: 18 | return None 19 | 20 | @staticmethod 21 | def get_users_with_username(username): 22 | users = list(User.objects.filter(username__contains=username).values('id', 23 | 'username', 24 | 'email')) 25 | return users 26 | 27 | @staticmethod 28 | def get_default_group(): 29 | try: 30 | group = Group.objects.get(name='default_group') 31 | return group 32 | except: 33 | return None 34 | -------------------------------------------------------------------------------- /dal/dao/action_dao.py: -------------------------------------------------------------------------------- 1 | from dal.models import * 2 | import datetime 3 | from dal.dao.account_dao import AccountDao 4 | from django.db.models import * 5 | from datetime import timedelta, datetime 6 | import re 7 | 8 | 9 | class ActionDao: 10 | @staticmethod 11 | def add_action(action): 12 | action.save() 13 | 14 | @staticmethod 15 | def get_daily_active_info(total): 16 | infos = [] 17 | counter = total - 1 18 | while counter >= 0: 19 | info = {} 20 | start_date = datetime.today() + timedelta(-counter - 1) 21 | end_date = datetime.today() + timedelta(-counter) 22 | info['日期'] = end_date 23 | visits = Action.objects.filter(create_time__gte=start_date, create_time__lt=end_date).values('id').count() 24 | info['访问量'] = visits 25 | mock_visits = Action.objects.filter(create_time__gte=start_date, create_time__lt=end_date, 26 | type=ActionType.Mock.value).values( 27 | 'id').count() 28 | info['Mock次数'] = mock_visits 29 | infos.append(info) 30 | counter -= 1 31 | # result = Action.objects.annotate(日期=TruncDay('create_time')).values('日期').annotate(访问量=Count('id')).values( 32 | # '日期', '访问量').order_by('日期')[:7] 33 | return list(infos) 34 | 35 | @staticmethod 36 | def get_top_active_users_info(limit): 37 | week_ago = datetime.today() + timedelta(-7) 38 | infos = Action.objects.filter(create_time__gte=week_ago).exclude(user_id__isnull=True).values( 39 | 'user_id').annotate( 40 | count=Count('id')).order_by('-count')[:limit] 41 | for info in infos: 42 | user = AccountDao.get_userProfile_with_id(info['user_id']) 43 | info['username'] = user.user.username 44 | return list(infos) 45 | 46 | @staticmethod 47 | def get_top_active_apis_info(limit): 48 | week_ago = datetime.today() + timedelta(-7) 49 | infos = Action.objects.filter(create_time__gte=week_ago, type=ActionType.Mock.value).values( 50 | 'request_info').annotate( 51 | count=Count('id')).order_by('-count')[:limit] 52 | for info in infos: 53 | request_info = info['request_info'] 54 | method = str.split(request_info, ' ')[1] 55 | result = re.search("/mock/[^\s]*/[^\s]*'", request_info) 56 | path = result.group(0).replace("'", "") 57 | project_id = int(str.split(path, '/')[2]) 58 | info['project_id'] = project_id 59 | # path = path.replace("/mock/", "").replace(str(project_id)+ "/", "") 60 | info['path'] = path 61 | return list(infos) 62 | -------------------------------------------------------------------------------- /dal/dao/api_dao.py: -------------------------------------------------------------------------------- 1 | from dal.models import Api 2 | import datetime 3 | 4 | 5 | class ApiDao: 6 | @staticmethod 7 | def create(model): 8 | api = Api.objects.create(path=model.path, 9 | method=model.method, 10 | name=model.name, 11 | note=model.note, 12 | status=model.status, 13 | resTemplate=model.resTemplate) 14 | return api 15 | 16 | @staticmethod 17 | def get_all_apis(): 18 | apis = Api.objects.all() 19 | return apis 20 | 21 | @staticmethod 22 | def get_all_api_list(): 23 | apis = list(ApiDao.get_all_apis().values('api_id', 24 | 'path', 25 | 'method', 26 | 'name', 27 | 'note', 28 | 'status', 29 | 'project')) 30 | return apis 31 | 32 | @staticmethod 33 | def get_all_stared_apis(offset, limit): 34 | apis = Api.objects.filter(star__exact=True).order_by('-createTime')[offset: offset + limit] 35 | return apis 36 | 37 | @staticmethod 38 | def get_stared_apis_count(): 39 | result = Api.objects.filter(star__exact=True).order_by('-createTime').count() 40 | return result 41 | 42 | @staticmethod 43 | def get_api_with_id(api_id): 44 | try: 45 | api = Api.objects.get(api_id=api_id) 46 | return api 47 | except: 48 | return None 49 | 50 | @staticmethod 51 | def get_apis_with_project_id(project_id, offset, limit): 52 | apis = Api.objects.filter(project__project_id=project_id).order_by('-createTime')[offset: offset + limit] 53 | return apis 54 | 55 | @staticmethod 56 | def get_all_apis_with_project_id(project_id): 57 | apis = Api.objects.filter(project__project_id=project_id).order_by('-createTime').values('api_id', 58 | 'path', 59 | 'method', 60 | 'name', 61 | 'note', 62 | 'status') 63 | return apis 64 | 65 | @staticmethod 66 | def get_apis_count_with_project_id(project_id): 67 | result = Api.objects.all().filter(project__project_id=project_id).count() 68 | return result 69 | 70 | @staticmethod 71 | def get_apis_with_project_id_and_path(project_id, path): 72 | apis = Api.objects.filter(project__project_id=project_id, path=path) 73 | return apis 74 | 75 | @staticmethod 76 | def get_api(project_id, path, method): 77 | try: 78 | api = Api.objects.get(project__project_id=project_id, path=path, method=method) 79 | return api 80 | except: 81 | return None 82 | 83 | @staticmethod 84 | def delete(api_id): 85 | deleted_count, _ = Api.objects.filter(api_id=api_id).delete() 86 | if deleted_count > 0: 87 | return True 88 | else: 89 | return False 90 | 91 | @staticmethod 92 | def delete_all(self): 93 | result = Api.objects.all().delete() 94 | 95 | @staticmethod 96 | def update(model): 97 | result = Api.objects.filter(api_id=model.api_id).update(path=model.path, 98 | method=model.method, 99 | name=model.name, 100 | note=model.note, 101 | status=model.status, 102 | star=model.star, 103 | resTemplate=model.resTemplate, 104 | updateTime=datetime.datetime.now()) 105 | if result > 0: 106 | return True 107 | else: 108 | return False 109 | -------------------------------------------------------------------------------- /dal/dao/project_dao.py: -------------------------------------------------------------------------------- 1 | from dal.models import Project 2 | from django.contrib.auth.models import User 3 | from dal.models import UserProfile 4 | from django.forms.models import model_to_dict 5 | from Sparrow.action.common_action import * 6 | import logging 7 | 8 | logger = logging.getLogger('django') 9 | 10 | 11 | class ProjectDao: 12 | @staticmethod 13 | def get_projects(offset, limit): 14 | projects = Project.objects.all().order_by('-updateTime')[offset: offset + limit] 15 | return projects 16 | 17 | @staticmethod 18 | def get_project_list(offset, limit): 19 | projects = list(ProjectDao.get_projects(offset, limit).values('project_id', 20 | 'name', 21 | 'note', 22 | 'status', 23 | 'createTime', 24 | 'updateTime')) 25 | return projects 26 | 27 | @staticmethod 28 | def get_project_list_with_user(user, offset, limit): 29 | userProfile = UserProfile.objects.get(user_id=user.id) 30 | result0 = Project.objects.filter(permissionType=Project.PermissionType.Public.value) 31 | result1 = userProfile.projects.filter() 32 | result = result0 | result1 33 | # 去重 34 | result = result.distinct() 35 | project_dics = list(result.values('project_id', 36 | 'name', 37 | 'note', 38 | 'status')[offset: offset + limit]) 39 | return project_dics 40 | 41 | @staticmethod 42 | def get_all_projects_count(): 43 | result = Project.objects.all().values('project_id').count() 44 | return result 45 | 46 | @staticmethod 47 | def get_project_with_id(project_id): 48 | try: 49 | project = Project.objects.get(project_id=project_id) 50 | return project 51 | except: 52 | return None 53 | 54 | @staticmethod 55 | def get_project_with_api_id(api_id): 56 | try: 57 | project = Project.objects.filter(apis__api_id=api_id).values('project_id', 58 | 'name', 59 | 'note', 60 | 'status', 61 | 'createTime', 62 | 'updateTime').first() 63 | return project 64 | except: 65 | return None 66 | 67 | @staticmethod 68 | def get_project_with_Name(name): 69 | try: 70 | project = Project.objects.get(name=name) 71 | return project 72 | except: 73 | return None 74 | 75 | @staticmethod 76 | def create(model): 77 | project = Project.objects.create(name=model.name, 78 | note=model.note, 79 | permissionType=model.permissionType, 80 | status=model.status) 81 | return project 82 | 83 | @staticmethod 84 | def update(model): 85 | result = Project.objects.filter(project_id=model.project_id).update(name=model.name, 86 | note=model.note, 87 | permissionType=model.permissionType, 88 | status=model.status, 89 | updateTime=datetime.datetime.now()) 90 | if result > 0: 91 | return True 92 | else: 93 | return False 94 | 95 | @staticmethod 96 | def delete(project_id): 97 | q = Api.objects.filter(project__project_id=project_id) 98 | q.delete() 99 | 100 | deleted_count, _ = Project.objects.filter(project_id=project_id).delete() 101 | if deleted_count > 0: 102 | return True 103 | else: 104 | return False 105 | 106 | @staticmethod 107 | def get_all_users_with_project_id(project_id): 108 | userProfiles = UserProfile.objects.filter(projects__project_id=project_id).values('user_id') 109 | users = [] 110 | for userProfile in userProfiles: 111 | user = list(User.objects.filter(id=userProfile['user_id']).values('id', 112 | 'username', 113 | 'email'))[0] 114 | users.append(user) 115 | 116 | return users 117 | 118 | @staticmethod 119 | def remove_user(project_id, user_id): 120 | project = ProjectDao.get_project_with_id(project_id) 121 | userProfile = UserProfile.objects.get(user__id=user_id) 122 | userProfile.projects.remove(project) 123 | userProfile.save() 124 | 125 | @staticmethod 126 | def add_users(project_id, user_ids): 127 | project = ProjectDao.get_project_with_id(project_id) 128 | userDics = [] 129 | for id in user_ids: 130 | userProfile = UserProfile.objects.get(user__id__in=id) 131 | userDic = list(User.objects.filter(id=id).values('id', 132 | 'username', 133 | 'email'))[0] 134 | userDics.append(userDic) 135 | userProfile.projects.add(project) 136 | userProfile.save() 137 | 138 | return userDics 139 | -------------------------------------------------------------------------------- /dal/dao/quick_login_record_dao.py: -------------------------------------------------------------------------------- 1 | from dal.models import * 2 | import datetime 3 | 4 | 5 | class QuickLoginRecordDao: 6 | @staticmethod 7 | def add_record(record): 8 | record.save() 9 | 10 | @staticmethod 11 | def get_record_with_user_id(user_id): 12 | try: 13 | record = QuickLoginRecord.objects.get(user_id=user_id) 14 | return record 15 | except: 16 | return None 17 | 18 | @staticmethod 19 | def update_record(record): 20 | result = QuickLoginRecord.objects.filter(id=record.id).update( 21 | verification_code=record.verification_code, 22 | update_time=datetime.datetime.now()) 23 | if result > 0: 24 | return True 25 | else: 26 | return False 27 | 28 | @staticmethod 29 | def get_record_with_verification_code(code): 30 | try: 31 | record = QuickLoginRecord.objects.get(verification_code=code) 32 | return record 33 | except: 34 | return None 35 | -------------------------------------------------------------------------------- /dal/dao/res_template_dao.py: -------------------------------------------------------------------------------- 1 | from dal.models import ResTemplate 2 | from dal.models import Api 3 | from dal.dao.api_dao import ApiDao 4 | import datetime 5 | 6 | 7 | class ResTemplateDao: 8 | @staticmethod 9 | def create(model): 10 | res_template = ResTemplate.objects.create(name=model.name, 11 | note=model.note, 12 | type=model.type, 13 | mimeType=model.mimeType, 14 | responseJson=model.responseJson, 15 | project=model.project) 16 | return res_template 17 | 18 | @staticmethod 19 | def get_all_public_res_templates(offset, limit): 20 | res_templates = ResTemplate.objects.filter(type=ResTemplate.Type.Public.value).order_by('-updateTime')[offset: offset + limit].values( 21 | 'res_template_id', 22 | 'name', 23 | 'note', 24 | 'mimeType', 25 | 'responseJson', 26 | 'updateTime') 27 | return res_templates 28 | 29 | @staticmethod 30 | def get_all_public_res_template_count(): 31 | result = ResTemplate.objects.filter(type=ResTemplate.Type.Public.value).values('res_template_id').count() 32 | return result 33 | 34 | @staticmethod 35 | def get_all_public_res_template_list(offset, limit): 36 | res_templates = list(ResTemplateDao.get_all_public_res_templates(offset, limit)) 37 | return res_templates 38 | 39 | @staticmethod 40 | def get_res_template_with_id(id): 41 | res_templates = ResTemplate.objects.get(res_template_id=id) 42 | return res_templates 43 | 44 | @staticmethod 45 | def get_res_templates_with_project_id(project_id): 46 | res_templates = ResTemplate.objects.filter(project__project_id=project_id) 47 | return res_templates 48 | 49 | @staticmethod 50 | def get_public_res_template_with_name(name): 51 | try: 52 | res_templates = ResTemplate.objects.filter(name=name) 53 | return res_templates.first() 54 | except: 55 | return None 56 | 57 | @staticmethod 58 | def get_private_res_template_with_api_id(api_id): 59 | try: 60 | api = ApiDao.get_api_with_id(api_id) 61 | if api is None: 62 | return None 63 | res = api.resTemplate 64 | return res 65 | except: 66 | return None 67 | 68 | @staticmethod 69 | def delete(res_template_id): 70 | deleted_count, _ = ResTemplate.objects.filter(res_template_id=res_template_id).delete() 71 | if deleted_count > 0: 72 | return True 73 | else: 74 | return False 75 | 76 | @staticmethod 77 | def update(model): 78 | result = ResTemplate.objects.filter(res_template_id=model.res_template_id).update( 79 | name=model.name, 80 | note=model.note, 81 | type=model.type, 82 | mimeType=model.mimeType, 83 | responseJson=model.responseJson, 84 | updateTime=datetime.datetime.now()) 85 | if result > 0: 86 | return True 87 | else: 88 | return False 89 | -------------------------------------------------------------------------------- /dal/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-04-05 07:02 2 | 3 | import dal.models 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ('auth', '0009_alter_user_last_name_max_length'), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Api', 20 | fields=[ 21 | ('api_id', models.AutoField(primary_key=True, serialize=False)), 22 | ('path', models.CharField(max_length=128)), 23 | ('method', models.CharField(max_length=8)), 24 | ('name', models.CharField(max_length=128, null=True)), 25 | ('note', models.CharField(default='', max_length=512, null=True)), 26 | ('status', models.IntegerField(default=0)), 27 | ('responseJson', models.TextField(blank=True, default='{}', null=True)), 28 | ('star', models.BooleanField(default=False)), 29 | ('createTime', models.DateTimeField(auto_now_add=True)), 30 | ('updateTime', models.DateTimeField(auto_now=True)), 31 | ], 32 | bases=(models.Model, dal.models.Dictable), 33 | ), 34 | migrations.CreateModel( 35 | name='Project', 36 | fields=[ 37 | ('project_id', models.AutoField(primary_key=True, serialize=False)), 38 | ('name', models.CharField(max_length=128, null=True)), 39 | ('note', models.CharField(default='', max_length=512, null=True)), 40 | ('status', models.IntegerField(default=1)), 41 | ('permissionType', models.IntegerField(default=0)), 42 | ('createTime', models.DateTimeField(auto_now_add=True)), 43 | ('updateTime', models.DateTimeField(auto_now=True)), 44 | ('apis', models.ManyToManyField(to='dal.Api')), 45 | ], 46 | options={ 47 | 'permissions': (('view_project', 'Can see detail of project '),), 48 | }, 49 | bases=(models.Model, dal.models.Dictable), 50 | ), 51 | migrations.CreateModel( 52 | name='ResTemplate', 53 | fields=[ 54 | ('res_template_id', models.AutoField(primary_key=True, serialize=False)), 55 | ('name', models.CharField(max_length=128, null=True)), 56 | ('note', models.CharField(default='', max_length=512, null=True)), 57 | ('mimeType', models.IntegerField(default=0)), 58 | ('responseJson', models.TextField(blank=True, default='{}', null=True)), 59 | ('createTime', models.DateTimeField(auto_now_add=True)), 60 | ('updateTime', models.DateTimeField(auto_now=True)), 61 | ], 62 | bases=(models.Model, dal.models.Dictable), 63 | ), 64 | migrations.CreateModel( 65 | name='UserProfile', 66 | fields=[ 67 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL)), 68 | ('projects', models.ManyToManyField(related_name='dal_userprofile_related', to='dal.Project')), 69 | ], 70 | bases=(models.Model, dal.models.Dictable), 71 | ), 72 | migrations.AddField( 73 | model_name='project', 74 | name='responseTemplates', 75 | field=models.ManyToManyField(to='dal.ResTemplate'), 76 | ), 77 | ] 78 | -------------------------------------------------------------------------------- /dal/migrations/0002_action.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-05-16 07:32 2 | 3 | import dal.models 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('dal', '0001_initial'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Action', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('type', models.IntegerField(default=0)), 22 | ('info', models.CharField(default='', max_length=512, null=True)), 23 | ('createTime', models.DateTimeField(auto_now_add=True)), 24 | ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | bases=(models.Model, dal.models.Dictable), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /dal/migrations/0003_auto_20180516_0736.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-05-16 07:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('dal', '0002_action'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='action', 15 | old_name='createTime', 16 | new_name='create_time', 17 | ), 18 | migrations.RenameField( 19 | model_name='action', 20 | old_name='info', 21 | new_name='function_info', 22 | ), 23 | migrations.AddField( 24 | model_name='action', 25 | name='request_info', 26 | field=models.CharField(default='', max_length=512, null=True), 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /dal/migrations/0004_quickloginrecord.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-05-17 08:07 2 | 3 | import dal.models 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 13 | ('dal', '0003_auto_20180516_0736'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='QuickLoginRecord', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('create_time', models.DateTimeField(auto_now_add=True)), 22 | ('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), 23 | ], 24 | bases=(models.Model, dal.models.Dictable), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /dal/migrations/0005_quickloginrecord_verification_code.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-05-17 08:13 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('dal', '0004_quickloginrecord'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='quickloginrecord', 15 | name='verification_code', 16 | field=models.CharField(default='', max_length=512, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /dal/migrations/0006_auto_20180517_0815.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-05-17 08:15 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('dal', '0005_quickloginrecord_verification_code'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='quickloginrecord', 15 | name='verification_code', 16 | field=models.CharField(default='', max_length=32, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /dal/migrations/0007_quickloginrecord_update_time.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-05-18 03:18 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('dal', '0006_auto_20180517_0815'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='quickloginrecord', 15 | name='update_time', 16 | field=models.DateTimeField(auto_now=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /dal/migrations/0008_restemplate_type.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-06-15 03:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('dal', '0007_quickloginrecord_update_time'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='restemplate', 15 | name='type', 16 | field=models.IntegerField(default=0), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /dal/migrations/0009_auto_20180615_0743.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-06-15 07:43 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dal', '0008_restemplate_type'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='project', 16 | name='responseTemplates', 17 | ), 18 | migrations.AddField( 19 | model_name='restemplate', 20 | name='project', 21 | field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='dal.Project'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /dal/migrations/0010_auto_20180615_0806.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-06-15 08:06 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dal', '0009_auto_20180615_0743'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='restemplate', 16 | name='project', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='dal.Project'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /dal/migrations/0011_auto_20180615_0916.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-06-15 09:16 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dal', '0010_auto_20180615_0806'), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name='api', 16 | name='responseJson', 17 | ), 18 | migrations.AddField( 19 | model_name='api', 20 | name='resTemplate', 21 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='dal.ResTemplate'), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /dal/migrations/0012_api_responsejson.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-06-15 09:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('dal', '0011_auto_20180615_0916'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='api', 15 | name='responseJson', 16 | field=models.TextField(blank=True, default='{}', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /dal/migrations/0013_auto_20180615_1011.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-06-15 10:11 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dal', '0012_api_responsejson'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='api', 16 | name='resTemplate', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='dal.ResTemplate'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /dal/migrations/0014_auto_20180615_1012.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-06-15 10:12 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dal', '0013_auto_20180615_1011'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='api', 16 | name='resTemplate', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='dal.ResTemplate'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /dal/migrations/0015_auto_20180615_1015.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-06-15 10:15 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('dal', '0014_auto_20180615_1012'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='api', 16 | name='resTemplate', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='dal.ResTemplate'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /dal/migrations/0016_remove_api_responsejson.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0 on 2018-06-19 07:09 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('dal', '0015_auto_20180615_1015'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RemoveField( 14 | model_name='api', 15 | name='responseJson', 16 | ), 17 | ] 18 | -------------------------------------------------------------------------------- /dal/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/dal/migrations/__init__.py -------------------------------------------------------------------------------- /dal/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from enum import Enum, unique 3 | from django.forms.models import model_to_dict 4 | from django.contrib.auth.models import User 5 | from django.db.models.signals import post_save 6 | from django.contrib.auth.models import Group 7 | 8 | 9 | class Dictable(object): 10 | def as_dict(self): 11 | dict = {} 12 | # exclude ManyToOneRel, which backwards to ForeignKey 13 | field_names = [] 14 | for field in self._meta.get_fields(): 15 | if 'ManyToOneRel' not in str(field) and 'ManyToManyRel' not in str(field): 16 | field_names.append(field.name) 17 | for name in field_names: 18 | field_instance = getattr(self, name) 19 | if field_instance.__class__.__name__ == 'ManyRelatedManager': 20 | model_dics = [] 21 | for model in field_instance.all(): 22 | model_dics.append(model_to_dict(model)) 23 | dict[name] = model_dics 24 | continue 25 | dict[name] = field_instance 26 | return dict 27 | 28 | 29 | class ResTemplate(models.Model, Dictable): 30 | res_template_id = models.AutoField(primary_key=True) 31 | name = models.CharField(max_length=128, null=True) 32 | note = models.CharField(max_length=512, null=True, default='') 33 | mimeType = models.IntegerField(default=0) 34 | responseJson = models.TextField(default='{}', blank=True, null=True) 35 | type = models.IntegerField(default=0, null=False) 36 | project = models.ForeignKey('Project', on_delete=models.SET_NULL, null=True) 37 | 38 | createTime = models.DateTimeField(auto_now_add=True) 39 | updateTime = models.DateTimeField(auto_now=True) 40 | 41 | @unique 42 | class MIMEType(Enum): 43 | ApplicationJson = 0 44 | TextPlain = 1 45 | ImageJpeg = 2 46 | 47 | @unique 48 | class Type(Enum): 49 | Public = 0 50 | BelongsToProject = 1 51 | 52 | 53 | class Api(models.Model, Dictable): 54 | api_id = models.AutoField(primary_key=True) 55 | path = models.CharField(max_length=128) 56 | method = models.CharField(max_length=8) 57 | name = models.CharField(max_length=128, null=True) 58 | note = models.CharField(max_length=512, null=True, default='') 59 | status = models.IntegerField(default=0) 60 | star = models.BooleanField(default=False) 61 | resTemplate = models.ForeignKey(ResTemplate, on_delete=models.CASCADE, null=True) 62 | 63 | createTime = models.DateTimeField(auto_now_add=True) 64 | updateTime = models.DateTimeField(auto_now=True) 65 | 66 | @unique 67 | class Status(Enum): 68 | Disabled = 0 69 | Mock = 1 70 | # 使用其他环境 71 | UseOther = 2 72 | 73 | 74 | class Project(models.Model, Dictable): 75 | project_id = models.AutoField(primary_key=True) 76 | name = models.CharField(max_length=128, null=True) 77 | note = models.CharField(max_length=512, null=True, default='') 78 | status = models.IntegerField(default=1) 79 | permissionType = models.IntegerField(default=0) 80 | apis = models.ManyToManyField(Api) 81 | 82 | createTime = models.DateTimeField(auto_now_add=True) 83 | updateTime = models.DateTimeField(auto_now=True) 84 | 85 | @unique 86 | class Status(Enum): 87 | Disabled = 0 88 | Abled = 1 89 | 90 | @unique 91 | class PermissionType(Enum): 92 | # 所有人可见 93 | Public = 0 94 | # 用户自行配置 95 | Custom = 1 96 | 97 | class Meta: 98 | permissions = ( 99 | ("view_project", "Can see detail of project "), 100 | ) 101 | 102 | 103 | class UserProfile(models.Model, Dictable): 104 | user = models.OneToOneField( 105 | User, 106 | on_delete=models.CASCADE, 107 | primary_key=True, ) 108 | projects = models.ManyToManyField( 109 | Project, 110 | related_name='%(app_label)s_%(class)s_related') 111 | 112 | def create(self, *args, **kwargs): 113 | if not self.pk: 114 | try: 115 | p = UserProfile.objects.get(user=self.user) 116 | self.pk = p.pk 117 | except UserProfile.DoesNotExist: 118 | pass 119 | super(UserProfile, self).save(*args, **kwargs) 120 | 121 | 122 | def create_user_profile(sender, instance, created, **kwargs): 123 | print(kwargs) 124 | if created: 125 | profile, created = UserProfile.objects.get_or_create(user=instance) 126 | 127 | 128 | def add_user_to_default_group(sender, instance, created, **kwargs): 129 | print(kwargs) 130 | if created: 131 | profile, created = UserProfile.objects.get_or_create(user=instance) 132 | try: 133 | default_group = Group.objects.get(name='default_group') 134 | except: 135 | default_group = Group.objects.create(name='default_group') 136 | default_group.user_set.add(instance) 137 | 138 | 139 | post_save.connect(create_user_profile, sender=User) 140 | post_save.connect(add_user_to_default_group, sender=User) 141 | 142 | 143 | # 数据统计部分 144 | @unique 145 | class ActionType(Enum): 146 | Mock = 0 147 | 148 | ProjectList = 1 149 | ProjectDetail = 2 150 | ProjectCreate = 3 151 | ProjectUpdate = 4 152 | ProjectRepeatNameVerification = 5 153 | ProjectDelete = 6 154 | ProjectGetMembers = 7 155 | ProjectRemoveMember = 8 156 | ProjectAddMember = 9 157 | 158 | ApiFetch = 10 159 | ApiCreate = 11 160 | ApiList = 12 161 | ApiRepeatNameVerification = 13 162 | ApiDelete = 14 163 | ApiDetail = 15 164 | ApiUpdate = 16 165 | ApiStar = 17 166 | ApiUpdateStatus = 18 167 | ApiStarList = 19 168 | ApiBatchUpdateStatus = 33 169 | 170 | ResTemplateList = 20 171 | ResTemplateDetail = 21 172 | ResTemplateCreate = 22 173 | ResTemplateRepeatNameVerification = 23 174 | ResTemplateDelete = 24 175 | ResTemplateUpdate = 25 176 | 177 | AccountLogin = 26 178 | AccountLogout = 27 179 | AccountRedirectLogin = 28 180 | AccountCheckStatus = 29 181 | AccountSearch = 30 182 | AccountQuickLogin = 31 183 | AccountRequestQuickLogin = 32 184 | 185 | ActionDailyActiveInfo = 34 186 | ActionTopActiveUserInfo = 35 187 | ActionTopActiveApisInfo = 36 188 | 189 | 190 | class Action(models.Model, Dictable): 191 | # 操作类型 192 | type = models.IntegerField(default=0) 193 | function_info = models.CharField(max_length=512, null=True, default='') 194 | request_info = models.CharField(max_length=512, null=True, default='') 195 | user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) 196 | create_time = models.DateTimeField(auto_now_add=True) 197 | 198 | 199 | class QuickLoginRecord(models.Model, Dictable): 200 | user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True) 201 | create_time = models.DateTimeField(auto_now_add=True) 202 | update_time = models.DateTimeField(auto_now=True) 203 | verification_code = models.CharField(max_length=32, null=True, default='') 204 | -------------------------------------------------------------------------------- /dal/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /dal/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /frontend/.appveyor.yml.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/.appveyor.yml.swp -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }], 4 | "stage-2" 5 | ], 6 | "plugins": [ 7 | "transform-export-extensions" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | build/build.js 2 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 8 | extends: 'standard', 9 | // required to lint *.vue files 10 | plugins: [ 11 | 'html' 12 | ], 13 | // add your custom rules here 14 | 'rules': { 15 | // allow paren-less arrow functions 16 | 'arrow-parens': 0, 17 | // allow async-await 18 | 'generator-star-spacing': 0, 19 | // allow debugger during development 20 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | # dist/ 4 | .idea 5 | npm-debug.log 6 | yarn-error.log 7 | selenium-debug.log 8 | test/unit/coverage 9 | test/e2e/reports -------------------------------------------------------------------------------- /frontend/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "5" 5 | - "6" 6 | 7 | before_install: 8 | - npm install npm@latest -g 9 | 10 | script: 11 | - node --version 12 | - npm --version 13 | - npm run build 14 | -------------------------------------------------------------------------------- /frontend/appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '4' 4 | - nodejs_version: '5' 5 | - nodejs_version: '6' 6 | 7 | install: 8 | - ps: Install-Product node $env:nodejs_version 9 | - npm install npm@latest -g 10 | - npm install 11 | 12 | test_script: 13 | - node --version 14 | - npm --version 15 | - npm run build 16 | 17 | build: off 18 | 19 | version: "{build}" 20 | -------------------------------------------------------------------------------- /frontend/backers.md: -------------------------------------------------------------------------------- 1 | # Backers 2 | 3 | Thank you for your supports! 4 | 5 | * [WerGimity](https://www.patreon.com/user/creators?u=5268464) 6 | 7 | * [Thomas_Leong](https://www.patreon.com/user/creators?u=5244543) 8 | 9 | * [wen](https://www.patreon.com/user/creators?u=5239734) 10 | 11 | * [Joris Vanhecke](https://www.patreon.com/user/creators?u=5145359) 12 | 13 | * [datastream](https://www.patreon.com/user/creators?u=4315833) 14 | 15 | * [Lê Chương](https://www.patreon.com/user/creators?u=3495305) 16 | -------------------------------------------------------------------------------- /frontend/build/build.js: -------------------------------------------------------------------------------- 1 | // https://github.com/shelljs/shelljs 2 | 'use strict' 3 | 4 | require('./check-versions')() 5 | require('shelljs/global') 6 | 7 | env.NODE_ENV = 'production' 8 | 9 | const ora = require('ora') 10 | const path = require('path') 11 | const chalk = require('chalk') 12 | const webpack = require('webpack') 13 | const config = require('../config') 14 | const webpackConfig = require('./webpack.prod.conf') 15 | 16 | const spinner = ora('building for production...') 17 | spinner.start() 18 | 19 | const assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory) 20 | rm('-rf', assetsPath) 21 | mkdir('-p', assetsPath) 22 | cp('-R', 'assets/*', assetsPath) 23 | 24 | const compiler = webpack(webpackConfig) 25 | const ProgressPlugin = require('webpack/lib/ProgressPlugin') 26 | compiler.apply(new ProgressPlugin()) 27 | 28 | compiler.run((err, stats) => { 29 | spinner.stop() 30 | if (err) throw err 31 | process.stdout.write(stats.toString({ 32 | colors: true, 33 | modules: false, 34 | children: false, 35 | chunks: false, 36 | chunkModules: false 37 | }) + '\n\n') 38 | 39 | console.log(chalk.cyan(' Build complete.\n')) 40 | console.log(chalk.yellow( 41 | ' Tip: built files are meant to be served over an HTTP server.\n' + 42 | ' Opening index.html over file:// won\'t work.\n' 43 | )) 44 | }) 45 | -------------------------------------------------------------------------------- /frontend/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const chalk = require('chalk') 4 | const semver = require('semver') 5 | const packageConfig = require('../package.json') 6 | 7 | const exec = (cmd) => { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | }, 17 | { 18 | name: 'npm', 19 | currentVersion: exec('npm --version'), 20 | versionRequirement: packageConfig.engines.npm 21 | } 22 | ] 23 | 24 | module.exports = () => { 25 | const warnings = [] 26 | for (let i = 0; i < versionRequirements.length; i++) { 27 | const mod = versionRequirements[i] 28 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 29 | warnings.push(mod.name + ': ' + 30 | chalk.red(mod.currentVersion) + ' should be ' + 31 | chalk.green(mod.versionRequirement) 32 | ) 33 | } 34 | } 35 | 36 | if (warnings.length) { 37 | console.log('') 38 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 39 | console.log() 40 | for (let i = 0; i < warnings.length; i++) { 41 | const warning = warnings[i] 42 | console.log(' ' + warning) 43 | } 44 | console.log() 45 | process.exit(1) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/build/dev-client.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /* eslint-disable */ 4 | require('eventsource-polyfill') 5 | const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 6 | 7 | hotClient.subscribe(event => { 8 | if (event.action === 'reload') { 9 | window.location.reload() 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /frontend/build/dev-server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('./check-versions')() 4 | 5 | const path = require('path') 6 | const express = require('express') 7 | const webpack = require('webpack') 8 | const opn = require('opn') 9 | const config = require('../config') 10 | const proxyMiddleware = require('http-proxy-middleware') 11 | const webpackConfig = process.env.NODE_ENV === 'testing' 12 | ? require('./webpack.prod.conf') 13 | : require('./webpack.dev.conf') 14 | 15 | // default port where dev server listens for incoming traffic 16 | const port = process.env.PORT || config.dev.port 17 | // automatically open browser, if not set will be false 18 | const autoOpenBrowser = Boolean(config.dev.autoOpenBrowser) 19 | // Define HTTP proxies to your custom API backend 20 | // https://github.com/chimurai/http-proxy-middleware 21 | const proxyTable = config.dev.proxyTable 22 | 23 | const app = express() 24 | const compiler = webpack(webpackConfig) 25 | 26 | const devMiddleware = require('webpack-dev-middleware')(compiler, { 27 | publicPath: webpackConfig.output.publicPath, 28 | stats: { 29 | colors: true, 30 | chunks: false 31 | } 32 | }) 33 | 34 | const hotMiddleware = require('webpack-hot-middleware')(compiler) 35 | // force page reload when html-webpack-plugin template changes 36 | compiler.plugin('compilation', compilation => { 37 | compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => { 38 | hotMiddleware.publish({ action: 'reload' }) 39 | cb() 40 | }) 41 | }) 42 | 43 | // proxy api requests 44 | Object.keys(proxyTable).forEach(context => { 45 | let options = proxyTable[context] 46 | if (typeof options === 'string') { 47 | options = { target: options } 48 | } 49 | app.use(proxyMiddleware(options.filter || context, options)) 50 | }) 51 | 52 | // handle fallback for HTML5 history API 53 | app.use(require('connect-history-api-fallback')()) 54 | 55 | // serve webpack bundle output 56 | app.use(devMiddleware) 57 | 58 | // enable hot-reload and state-preserving 59 | // compilation error display 60 | app.use(hotMiddleware) 61 | 62 | // serve pure static assets 63 | const staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 64 | app.use(staticPath, express.static('./assets')) 65 | 66 | const uri = 'http://localhost:' + port 67 | 68 | devMiddleware.waitUntilValid(() => { 69 | console.log('> Listening at ' + uri + '\n') 70 | }) 71 | 72 | module.exports = app.listen(port, err => { 73 | if (err) { 74 | console.log(err) 75 | return 76 | } 77 | 78 | // when env is testing, don't need open it 79 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 80 | opn(uri) 81 | } 82 | }) 83 | -------------------------------------------------------------------------------- /frontend/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const config = require('../config') 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 6 | 7 | exports.assetsPath = _path => { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | return path.posix.join(assetsSubDirectory, _path) 12 | } 13 | 14 | exports.cssLoaders = options => { 15 | options = options || {} 16 | // generate loader string to be used with extract text plugin 17 | const generateLoaders = loaders => { 18 | const sourceLoader = loaders.map(loader => { 19 | let extraParamChar 20 | if (/\?/.test(loader)) { 21 | loader = loader.replace(/\?/, '-loader?') 22 | extraParamChar = '&' 23 | } else { 24 | loader = loader + '-loader' 25 | extraParamChar = '?' 26 | } 27 | return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '') 28 | }).join('!') 29 | 30 | // Extract CSS when that option is specified 31 | // (which is the case during production build) 32 | if (options.extract) { 33 | return ExtractTextPlugin.extract({ 34 | use: sourceLoader, 35 | fallback: 'vue-style-loader' 36 | }) 37 | } else { 38 | return ['vue-style-loader', sourceLoader].join('!') 39 | } 40 | } 41 | 42 | // http://vuejs.github.io/vue-loader/configurations/extract-css.html 43 | return { 44 | css: generateLoaders(['css']), 45 | postcss: generateLoaders(['css']), 46 | less: generateLoaders(['css', 'less']), 47 | sass: generateLoaders(['css', 'sass?indentedSyntax']), 48 | scss: generateLoaders(['css', 'sass']), 49 | stylus: generateLoaders(['css', 'stylus']), 50 | styl: generateLoaders(['css', 'stylus']) 51 | } 52 | } 53 | 54 | // Generate loaders for standalone style files (outside of .vue) 55 | exports.styleLoaders = options => { 56 | const output = [] 57 | const loaders = exports.cssLoaders(options) 58 | for (const extension of Object.keys(loaders)) { 59 | const loader = loaders[extension] 60 | output.push({ 61 | test: new RegExp('\\.' + extension + '$'), 62 | loader: loader 63 | }) 64 | } 65 | return output 66 | } 67 | -------------------------------------------------------------------------------- /frontend/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | 6 | const isProduction = process.env.NODE_ENV === 'production' 7 | 8 | module.exports = { 9 | loaders: utils.cssLoaders({ 10 | sourceMap: isProduction ? config.build.productionSourceMap : config.dev.cssSourceMap, 11 | extract: isProduction 12 | }), 13 | postcss: [ 14 | require('autoprefixer')({ 15 | browsers: ['last 3 versions'] 16 | }) 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /frontend/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const config = require('../config') 5 | const utils = require('./utils') 6 | const projectRoot = path.resolve(__dirname, '../') 7 | 8 | module.exports = { 9 | entry: { 10 | app: ['./client/index.js'], 11 | // If you want to support IE < 11, should add `babel-polyfill` to vendor. 12 | // e.g. ['babel-polyfill', 'vue', 'vue-router', 'vuex'] 13 | vendor: [ 14 | 'vue', 15 | 'vue-router', 16 | 'vuex', 17 | 'vuex-router-sync' 18 | ] 19 | }, 20 | output: { 21 | path: config.build.assetsRoot, 22 | publicPath: process.env.NODE_ENV === 'production' 23 | ? config.build.assetsPublicPath 24 | : config.dev.assetsPublicPath, 25 | filename: '[name].js' 26 | }, 27 | resolve: { 28 | extensions: ['.js', '.vue', '.css', '.json'], 29 | alias: { 30 | // https://github.com/vuejs/vue/wiki/Vue-2.0-RC-Starter-Resources 31 | // vue: 'vue/dist/vue', 32 | package: path.resolve(__dirname, '../package.json'), 33 | src: path.resolve(__dirname, '../client'), 34 | assets: path.resolve(__dirname, '../client/assets'), 35 | components: path.resolve(__dirname, '../client/components'), 36 | views: path.resolve(__dirname, '../client/views'), 37 | // third-party 38 | 'plotly.js': 'plotly.js/dist/plotly', 39 | // vue-addon 40 | 'vuex-store': path.resolve(__dirname, '../client/store') 41 | } 42 | }, 43 | module: { 44 | loaders: [ 45 | { 46 | test: /\.(js|vue)$/, 47 | loader: 'eslint-loader', 48 | include: projectRoot, 49 | exclude: /node_modules/, 50 | enforce: 'pre', 51 | options: { 52 | formatter: require('eslint-friendly-formatter') 53 | } 54 | }, 55 | { 56 | test: /\.vue$/, 57 | loader: 'vue-loader', 58 | options: require('./vue-loader.conf') 59 | }, 60 | { 61 | test: /\.js$/, 62 | loader: 'babel-loader', 63 | include: projectRoot, 64 | // /node_modules\/(?!vue-bulma-.*)/ 65 | exclude: [new RegExp(`node_modules\\${path.sep}(?!vue-bulma-.*)`)] 66 | }, 67 | { 68 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 69 | loader: 'url-loader', 70 | query: { 71 | limit: 10000, 72 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 73 | } 74 | }, 75 | { 76 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 77 | loader: 'url-loader', 78 | query: { 79 | limit: 10000, 80 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 81 | } 82 | } 83 | ] 84 | }, 85 | // See https://github.com/webpack/webpack/issues/3486 86 | performance: { 87 | hints: false 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /frontend/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const webpack = require('webpack') 4 | const merge = require('webpack-merge') 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | const baseWebpackConfig = require('./webpack.base.conf') 7 | const config = require('../config') 8 | const utils = require('./utils') 9 | 10 | // add hot-reload related code to entry chunks 11 | Object.keys(baseWebpackConfig.entry).forEach(name => { 12 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 13 | }) 14 | 15 | module.exports = merge(baseWebpackConfig, { 16 | module: { 17 | loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 18 | }, 19 | // eval-source-map is faster for development 20 | devtool: '#eval-source-map', 21 | plugins: [ 22 | new webpack.DefinePlugin({ 23 | 'process.env': config.dev.env 24 | }), 25 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 26 | new webpack.HotModuleReplacementPlugin(), 27 | new webpack.NoEmitOnErrorsPlugin(), 28 | // extract vendor chunks for better caching 29 | new webpack.optimize.CommonsChunkPlugin({ 30 | name: 'vendor', 31 | filename: 'vendor.js' 32 | }), 33 | // https://github.com/ampedandwired/html-webpack-plugin 34 | new HtmlWebpackPlugin({ 35 | title: 'Sparrow', 36 | filename: 'index.html', 37 | template: 'index.html', 38 | inject: true, 39 | favicon: 'client/assets/logo.png' 40 | }) 41 | ] 42 | }) 43 | -------------------------------------------------------------------------------- /frontend/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const webpack = require('webpack') 5 | const merge = require('webpack-merge') 6 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 7 | const HtmlWebpackPlugin = require('html-webpack-plugin') 8 | const baseWebpackConfig = require('./webpack.base.conf') 9 | const config = require('../config') 10 | const utils = require('./utils') 11 | const env = process.env.NODE_ENV === 'testing' 12 | ? require('../config/test.env') 13 | : config.build.env 14 | const isELECTRON = process.env.NODE_ELECTRON === 'true' 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | loaders: 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 | publicPath: isELECTRON ? path.join(__dirname, '../dist/') : config.build.assetsPublicPath, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new webpack.LoaderOptionsPlugin({ 36 | minimize: true 37 | }), 38 | new webpack.optimize.UglifyJsPlugin({ 39 | 'screw-ie8': true, 40 | sourceMap: true, 41 | compress: { 42 | warnings: false 43 | }, 44 | output: { 45 | comments: false 46 | } 47 | }), 48 | // extract css into its own file 49 | new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')), 50 | // generate dist index.html with correct asset hash for caching. 51 | // you can customize output by editing /index.html 52 | // see https://github.com/ampedandwired/html-webpack-plugin 53 | new HtmlWebpackPlugin({ 54 | title: 'Sparrow', 55 | filename: process.env.NODE_ENV === 'testing' 56 | ? 'index.html' 57 | : config.build.index, 58 | template: 'index.html', 59 | inject: true, 60 | favicon: 'client/assets/logo.png', 61 | minify: { 62 | removeComments: true, 63 | collapseWhitespace: true, 64 | removeAttributeQuotes: true 65 | // more options: 66 | // https://github.com/kangax/html-minifier#options-quick-reference 67 | }, 68 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 69 | chunksSortMode: 'dependency' 70 | }), 71 | // split vendor js into its own file 72 | new webpack.optimize.CommonsChunkPlugin({ 73 | name: 'vendor', 74 | minChunks (module, count) { 75 | // any required modules inside node_modules are extracted to vendor 76 | return ( 77 | module.resource && 78 | /\.js$/.test(module.resource) && 79 | module.resource.indexOf( 80 | path.join(__dirname, '../node_modules') 81 | ) === 0 82 | ) 83 | } 84 | }), 85 | // extract webpack runtime and module manifest to its own file in order to 86 | // prevent vendor hash from being updated whenever app bundle is updated 87 | new webpack.optimize.CommonsChunkPlugin({ 88 | name: 'manifest', 89 | chunks: ['vendor'] 90 | }) 91 | ] 92 | }) 93 | 94 | if (config.build.productionGzip) { 95 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 96 | 97 | webpackConfig.plugins.push( 98 | new CompressionWebpackPlugin({ 99 | asset: '[path].gz[query]', 100 | algorithm: 'gzip', 101 | test: new RegExp( 102 | '\\.(' + 103 | config.build.productionGzipExtensions.join('|') + 104 | ')$' 105 | ), 106 | threshold: 10240, 107 | minRatio: 0.8 108 | }) 109 | ) 110 | } 111 | 112 | module.exports = webpackConfig 113 | -------------------------------------------------------------------------------- /frontend/client/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 56 | 57 | 98 | -------------------------------------------------------------------------------- /frontend/client/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import axios from 'axios' 3 | import VueAxios from 'vue-axios' 4 | import VueAuth from '@websanova/vue-auth' 5 | import NProgress from 'vue-nprogress' 6 | import {sync} from 'vuex-router-sync' 7 | import App from './App.vue' 8 | import router from './router' 9 | import store from './store' 10 | import * as filters from './filters' 11 | import {TOGGLE_SIDEBAR} from 'vuex-store/mutation-types' 12 | import VueCookie from 'vue-cookie' 13 | import ElementUI from 'element-ui' 14 | import VueHighlightJS from 'vue-highlightjs' 15 | 16 | Vue.router = router 17 | 18 | axios.defaults.withCredentials = true 19 | Vue.use(VueAxios, axios) 20 | 21 | Vue.use(VueCookie) 22 | 23 | Vue.use(ElementUI) 24 | 25 | Vue.use(VueHighlightJS) 26 | 27 | Vue.use(VueAuth, { 28 | auth: { 29 | request: function (req, token) { 30 | this.options.http._setHeaders.call(this, req, {Authorization: 'Bearer ' + token}) 31 | }, 32 | response: function (res) { 33 | // Get Token from response body 34 | return res.data 35 | } 36 | }, 37 | http: require('@websanova/vue-auth/drivers/http/axios.1.x.js'), 38 | router: require('@websanova/vue-auth/drivers/router/vue-router.2.x.js'), 39 | loginData: {url: '/frontend/user/login', fetchUser: false}, 40 | refreshData: {enabled: false} 41 | }) 42 | 43 | Vue.use(NProgress) 44 | 45 | // Enable devtools 46 | Vue.config.devtools = true 47 | 48 | sync(store, router) 49 | 50 | const nprogress = new NProgress({parent: '.nprogress-container'}) 51 | 52 | const {state} = store 53 | 54 | router.beforeEach((route, redirect, next) => { 55 | if (state.app.device.isMobile && state.app.sidebar.opened) { 56 | store.commit(TOGGLE_SIDEBAR, false) 57 | } 58 | next() 59 | }) 60 | 61 | Object.keys(filters).forEach(key => { 62 | Vue.filter(key, filters[key]) 63 | }) 64 | 65 | const app = new Vue({ 66 | router, 67 | store, 68 | nprogress, 69 | ...App 70 | }) 71 | 72 | export {app, router, store} 73 | -------------------------------------------------------------------------------- /frontend/client/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/client/assets/logo.png -------------------------------------------------------------------------------- /frontend/client/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo 5 | Created with Sketch. 6 | 7 | 17 | -------------------------------------------------------------------------------- /frontend/client/assets/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/client/assets/logo@2x.png -------------------------------------------------------------------------------- /frontend/client/components/layout/AppMain.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 35 | 36 | 55 | -------------------------------------------------------------------------------- /frontend/client/components/layout/FooterBar.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 34 | 35 | 54 | -------------------------------------------------------------------------------- /frontend/client/components/layout/Levelbar.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 77 | -------------------------------------------------------------------------------- /frontend/client/components/layout/Navbar.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 167 | 168 | 207 | -------------------------------------------------------------------------------- /frontend/client/components/layout/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 135 | 136 | 188 | -------------------------------------------------------------------------------- /frontend/client/components/layout/index.js: -------------------------------------------------------------------------------- 1 | export Navbar from './Navbar' 2 | 3 | export Sidebar from './Sidebar' 4 | 5 | export AppMain from './AppMain' 6 | 7 | export FooterBar from './FooterBar' 8 | -------------------------------------------------------------------------------- /frontend/client/filters/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/client/filters/index.js -------------------------------------------------------------------------------- /frontend/client/index.js: -------------------------------------------------------------------------------- 1 | import { app } from './app' 2 | 3 | app.$mount('#app') 4 | -------------------------------------------------------------------------------- /frontend/client/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import menuModule from 'vuex-store/modules/menu' 4 | Vue.use(Router) 5 | 6 | export default new Router({ 7 | mode: 'hash', // Demo is living in GitHub.io, so required! 8 | linkActiveClass: 'is-active', 9 | scrollBehavior: () => ({ y: 0 }), 10 | routes: [ 11 | { 12 | name: '403', 13 | path: '/403', 14 | component: require('../views/403') 15 | }, 16 | { 17 | name: 'Home', 18 | path: '/', 19 | component: require('../views/Home') 20 | }, 21 | { 22 | name: '登录', 23 | path: '/login', 24 | component: require('../views/auth/Login') 25 | }, 26 | { 27 | name: '项目 / 添加', 28 | path: '/project/create', 29 | component: require('../views/project/create') 30 | }, 31 | { 32 | name: '项目 / API', 33 | path: '/project/detail/:id', 34 | component: require('../views/project/detail') 35 | }, 36 | { 37 | name: '项目 / API / 添加', 38 | path: '/project/:project_id/api/create', 39 | component: require('../views/api/create') 40 | }, 41 | { 42 | name: '项目 / API / 更新', 43 | path: '/project/:project_id/api/update/:api_id', 44 | component: require('../views/api/update') 45 | }, 46 | { 47 | name: '项目 / API / 详情', 48 | path: '/project/:project_id/api/detail/:api_id', 49 | component: require('../views/api/detail') 50 | }, 51 | { 52 | name: '公共模板 / 添加', 53 | path: '/template/create', 54 | component: require('../views/template/create') 55 | }, 56 | { 57 | name: '公共模板 / 详情', 58 | path: '/template/detail/:id', 59 | component: require('../views/template/detail') 60 | }, 61 | { 62 | name: '公共模板 / 更新', 63 | path: '/template/update/:id', 64 | component: require('../views/template/update') 65 | }, 66 | ...generateRoutesFromMenu(menuModule.state.items), 67 | { 68 | path: '*', 69 | redirect: '/' 70 | } 71 | ] 72 | }) 73 | 74 | // Menu should have 2 levels. 75 | function generateRoutesFromMenu (menu = [], routes = []) { 76 | for (let i = 0, l = menu.length; i < l; i++) { 77 | let item = menu[i] 78 | if (item.path) { 79 | routes.push(item) 80 | } 81 | if (!item.component) { 82 | generateRoutesFromMenu(item.children, routes) 83 | } 84 | } 85 | return routes 86 | } 87 | -------------------------------------------------------------------------------- /frontend/client/store/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from './mutation-types' 2 | 3 | export const toggleSidebar = ({ commit }, config) => { 4 | if (config instanceof Object) { 5 | commit(types.TOGGLE_SIDEBAR, config) 6 | } 7 | } 8 | 9 | export const toggleDevice = ({ commit }, device) => commit(types.TOGGLE_DEVICE, device) 10 | 11 | export const expandMenu = ({ commit }, menuItem) => { 12 | if (menuItem) { 13 | menuItem.expanded = menuItem.expanded || false 14 | commit(types.EXPAND_MENU, menuItem) 15 | } 16 | } 17 | 18 | export const switchEffect = ({ commit }, effectItem) => { 19 | if (effectItem) { 20 | commit(types.SWITCH_EFFECT, effectItem) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /frontend/client/store/getters.js: -------------------------------------------------------------------------------- 1 | const pkg = state => state.pkg 2 | const app = state => state.app 3 | const device = state => state.app.device 4 | const sidebar = state => state.app.sidebar 5 | const effect = state => state.app.effect 6 | const menuitems = state => state.menu.items 7 | const componententry = state => { 8 | return state.menu.items.filter(c => c.meta && c.meta.label === 'Components')[0] 9 | } 10 | 11 | export { 12 | pkg, 13 | app, 14 | device, 15 | sidebar, 16 | effect, 17 | menuitems, 18 | componententry 19 | } 20 | -------------------------------------------------------------------------------- /frontend/client/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import pkg from 'package' 4 | import * as actions from './actions' 5 | import * as getters from './getters' 6 | 7 | import app from './modules/app' 8 | import menu from './modules/menu' 9 | 10 | Vue.use(Vuex) 11 | 12 | const store = new Vuex.Store({ 13 | strict: true, // process.env.NODE_ENV !== 'production', 14 | actions, 15 | getters, 16 | modules: { 17 | app, 18 | menu 19 | }, 20 | state: { 21 | pkg 22 | }, 23 | mutations: { 24 | } 25 | }) 26 | 27 | export default store 28 | -------------------------------------------------------------------------------- /frontend/client/store/modules/app.js: -------------------------------------------------------------------------------- 1 | import * as types from '../mutation-types' 2 | 3 | const state = { 4 | device: { 5 | isMobile: false, 6 | isTablet: false 7 | }, 8 | sidebar: { 9 | opened: false, 10 | hidden: false 11 | }, 12 | effect: { 13 | translate3d: true 14 | } 15 | } 16 | 17 | const mutations = { 18 | [types.TOGGLE_DEVICE] (state, device) { 19 | state.device.isMobile = device === 'mobile' 20 | state.device.isTablet = device === 'tablet' 21 | }, 22 | 23 | [types.TOGGLE_SIDEBAR] (state, config) { 24 | if (state.device.isMobile && config.hasOwnProperty('opened')) { 25 | state.sidebar.opened = config.opened 26 | } else { 27 | state.sidebar.opened = true 28 | } 29 | 30 | if (config.hasOwnProperty('hidden')) { 31 | state.sidebar.hidden = config.hidden 32 | } 33 | }, 34 | 35 | [types.SWITCH_EFFECT] (state, effectItem) { 36 | for (let name in effectItem) { 37 | state.effect[name] = effectItem[name] 38 | } 39 | } 40 | } 41 | 42 | export default { 43 | state, 44 | mutations 45 | } 46 | -------------------------------------------------------------------------------- /frontend/client/store/modules/menu/index.js: -------------------------------------------------------------------------------- 1 | import * as types from '../../mutation-types' 2 | import lazyLoading from './lazyLoading' 3 | 4 | // show: meta.label -> name 5 | // name: component name 6 | // meta.label: display label 7 | 8 | const state = { 9 | items: [ 10 | { 11 | name: 'Dashboard', 12 | path: '/dashboard', 13 | meta: { 14 | icon: 'fa-tachometer', 15 | link: 'dashboard/index.vue' 16 | }, 17 | component: lazyLoading('dashboard', true) 18 | }, 19 | { 20 | name: '项目', 21 | path: '/project', 22 | meta: { 23 | icon: 'fa-list-ul', 24 | link: 'project/index.vue' 25 | }, 26 | component: lazyLoading('project', true) 27 | }, 28 | { 29 | name: '公共返回模板', 30 | path: '/template', 31 | meta: { 32 | icon: 'fa-copy', 33 | link: 'template/index.vue' 34 | }, 35 | component: lazyLoading('template', true) 36 | }, 37 | { 38 | name: '收藏', 39 | path: '/favorite', 40 | meta: { 41 | icon: 'fa-heart-o', 42 | link: 'favorite/index.vue' 43 | }, 44 | component: lazyLoading('favorite', true) 45 | } 46 | ] 47 | } 48 | 49 | const mutations = { 50 | [types.EXPAND_MENU] (state, menuItem) { 51 | if (menuItem.index > -1) { 52 | if (state.items[menuItem.index] && state.items[menuItem.index].meta) { 53 | state.items[menuItem.index].meta.expanded = menuItem.expanded 54 | } 55 | } else if (menuItem.item && 'expanded' in menuItem.item.meta) { 56 | menuItem.item.meta.expanded = menuItem.expanded 57 | } 58 | } 59 | } 60 | 61 | export default { 62 | state, 63 | mutations 64 | } 65 | -------------------------------------------------------------------------------- /frontend/client/store/modules/menu/lazyLoading.js: -------------------------------------------------------------------------------- 1 | // lazy loading Components 2 | // https://github.com/vuejs/vue-router/blob/dev/examples/lazy-loading/app.js#L8 3 | export default (name, index = false) => () => import(`views/${name}${index ? '/index' : ''}.vue`) 4 | -------------------------------------------------------------------------------- /frontend/client/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const TOGGLE_DEVICE = 'TOGGLE_DEVICE' 2 | 3 | export const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR' 4 | 5 | export const EXPAND_MENU = 'EXPAND_MENU' 6 | 7 | export const SWITCH_EFFECT = 'SWITCH_EFFECT' 8 | -------------------------------------------------------------------------------- /frontend/client/views/403.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /frontend/client/views/Home.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | 27 | 32 | -------------------------------------------------------------------------------- /frontend/client/views/auth/Login.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 70 | 71 | 76 | -------------------------------------------------------------------------------- /frontend/client/views/axios/index.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 153 | 154 | 156 | -------------------------------------------------------------------------------- /frontend/client/views/components/APIList.vue: -------------------------------------------------------------------------------- 1 | 112 | 113 | 177 | 178 | 217 | -------------------------------------------------------------------------------- /frontend/client/views/components/JsonEditor.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 90 | 91 | 94 | -------------------------------------------------------------------------------- /frontend/client/views/components/Modal.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 93 | 94 | 96 | -------------------------------------------------------------------------------- /frontend/client/views/components/PopupView.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 91 | 92 | 117 | -------------------------------------------------------------------------------- /frontend/client/views/components/ProjectChooser.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 102 | 103 | 125 | -------------------------------------------------------------------------------- /frontend/client/views/components/TemplateChooser.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 135 | 136 | 158 | -------------------------------------------------------------------------------- /frontend/client/views/components/modals/CardModal.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 39 | -------------------------------------------------------------------------------- /frontend/client/views/components/modals/ImageModal.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 41 | 42 | 54 | -------------------------------------------------------------------------------- /frontend/client/views/components/modals/Modal.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 56 | -------------------------------------------------------------------------------- /frontend/client/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | 139 | 140 | 142 | -------------------------------------------------------------------------------- /frontend/client/views/favorite/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 72 | 73 | 75 | -------------------------------------------------------------------------------- /frontend/client/views/network.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import * as notification from './notification.js' 3 | import router from '../router' 4 | 5 | // 超时 6 | axios.defaults.timeout = 8000 7 | 8 | // http response 拦截器 9 | axios.interceptors.response.use( 10 | response => { 11 | return response 12 | }, 13 | error => { 14 | if (error.response) { 15 | switch (error.response.status) { 16 | case 404: 17 | console.log('请求404') 18 | break 19 | case 500: 20 | console.log('请求500') 21 | break 22 | case 403: 23 | router.push({path: '/403'}) 24 | break 25 | default: 26 | } 27 | } 28 | return Promise.reject(error) 29 | } 30 | ) 31 | 32 | // 封装请求 33 | export function request (url, options) { 34 | var opt = options || {} 35 | return new Promise((resolve, reject) => { 36 | axios({ 37 | method: opt.method || 'get', 38 | url: url, 39 | params: opt.params || {}, 40 | data: (opt.headers ? opt.data : opt.data) || {}, 41 | responseType: opt.dataType || 'json', 42 | headers: opt.headers || {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'} 43 | }) 44 | .then(response => { 45 | if (response.data.code === 200) { 46 | resolve(response.data) 47 | } else if (response.data.code === 901) { 48 | router.push({path: '/login'}) 49 | } else { 50 | reject(response.data) 51 | } 52 | }) 53 | .catch(error => { 54 | if (error.response.status === 403) { 55 | } else { 56 | console.log(error) 57 | notification.toast({ 58 | message: '网络访问错误', 59 | type: 'danger', 60 | duration: 2000 61 | }) 62 | } 63 | }) 64 | }) 65 | } 66 | 67 | export default {request} 68 | -------------------------------------------------------------------------------- /frontend/client/views/notification.js: -------------------------------------------------------------------------------- 1 | import Notification from 'vue-bulma-notification' 2 | import Vue from 'vue' 3 | 4 | const NotificationComponent = Vue.extend(Notification) 5 | 6 | export const toast = (propsData = { 7 | title: '', 8 | message: '', 9 | type: '', 10 | direction: '', 11 | duration: 4500, 12 | container: '.notifications' 13 | }) => { 14 | return new NotificationComponent({ 15 | el: document.createElement('div'), 16 | propsData 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /frontend/client/views/project/create.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 148 | 149 | 159 | -------------------------------------------------------------------------------- /frontend/client/views/project/detail.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 141 | 142 | 156 | -------------------------------------------------------------------------------- /frontend/client/views/project/index.vue: -------------------------------------------------------------------------------- 1 | 52 | 53 | 109 | 110 | 128 | -------------------------------------------------------------------------------- /frontend/client/views/template/detail.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 151 | 152 | 196 | -------------------------------------------------------------------------------- /frontend/client/views/template/index.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 100 | 101 | 119 | -------------------------------------------------------------------------------- /frontend/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const merge = require('webpack-merge') 4 | const prodEnv = require('./prod.env') 5 | 6 | module.exports = merge(prodEnv, { 7 | NODE_ENV: '"development"' 8 | }) 9 | -------------------------------------------------------------------------------- /frontend/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | const path = require('path') 5 | 6 | module.exports = { 7 | build: { 8 | env: require('./prod.env'), 9 | index: path.resolve(__dirname, '../dist/index.html'), 10 | assetsRoot: path.resolve(__dirname, '../dist'), 11 | assetsSubDirectory: 'assets', 12 | assetsPublicPath: '/static/', 13 | productionSourceMap: true, 14 | // Gzip off by default as many popular static hosts such as 15 | // Surge or Netlify already gzip all static assets for you. 16 | // Before setting to `true`, make sure to: 17 | // npm install --save-dev compression-webpack-plugin 18 | productionGzip: false, 19 | productionGzipExtensions: ['js', 'css'], 20 | }, 21 | dev: { 22 | env: require('./dev.env'), 23 | port: process.env.DEV_PORT || 8080, 24 | autoOpenBrowser: true, 25 | assetsSubDirectory: 'assets', 26 | assetsPublicPath: '/', 27 | proxyTable: { 28 | '/frontend': { 29 | target: 'http://localhost:8000', 30 | changeOrigin: true, 31 | pathRewrite: { 32 | '^/frontend': 'frontend' 33 | } 34 | }, 35 | '/mock': { 36 | target: 'http://localhost:8000', 37 | changeOrigin: true, 38 | pathRewrite: { 39 | '^/mock': 'mock' 40 | } 41 | } 42 | }, 43 | // CSS Sourcemaps off by default because relative paths are "buggy" 44 | // with this option, according to the CSS-Loader README 45 | // (https://github.com/webpack/css-loader#sourcemaps) 46 | // In our experience, they generally work as expected, 47 | // just be aware of this issue when enabling this option. 48 | cssSourceMap: false 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /frontend/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | NODE_ENV: '"production"' 5 | } 6 | -------------------------------------------------------------------------------- /frontend/config/test.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const merge = require('webpack-merge') 4 | const devEnv = require('./dev.env') 5 | 6 | module.exports = merge(devEnv, { 7 | NODE_ENV: '"testing"' 8 | }) 9 | -------------------------------------------------------------------------------- /frontend/dist/assets/fonts/element-icons.6f0a763.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/dist/assets/fonts/element-icons.6f0a763.ttf -------------------------------------------------------------------------------- /frontend/dist/assets/fonts/fontawesome-webfont.674f50d.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/dist/assets/fonts/fontawesome-webfont.674f50d.eot -------------------------------------------------------------------------------- /frontend/dist/assets/fonts/fontawesome-webfont.af7ae50.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/dist/assets/fonts/fontawesome-webfont.af7ae50.woff2 -------------------------------------------------------------------------------- /frontend/dist/assets/fonts/fontawesome-webfont.b06871f.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/dist/assets/fonts/fontawesome-webfont.b06871f.ttf -------------------------------------------------------------------------------- /frontend/dist/assets/fonts/fontawesome-webfont.fee66e7.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/dist/assets/fonts/fontawesome-webfont.fee66e7.woff -------------------------------------------------------------------------------- /frontend/dist/assets/js/4.23fab30cde5ebb4b6a96.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([4,6],{1012:function(t,e,a){var i=a(941);"string"==typeof i&&(i=[[t.i,i,""]]),i.locals&&(t.exports=i.locals);a(659)("ca61996c",i,!0)},1019:function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",[a("div",{staticClass:"tile is-ancestor"},[a("div",{staticClass:"tile is-parent is-4"},[a("article",{staticClass:"tile is-child box"},[a("h1",{staticClass:"title"},[t._v("Classic")]),t._v(" "),a("a",{staticClass:"button is-primary is-large modal-button",on:{click:t.openModalBasic}},[t._v("Launch example modal")])])]),t._v(" "),a("div",{staticClass:"tile is-parent is-4"},[a("article",{staticClass:"tile is-child box"},[a("h1",{staticClass:"title"},[t._v("Image")]),t._v(" "),a("a",{staticClass:"button is-primary is-large modal-button",on:{click:t.openModalImage}},[t._v("Launch image modal")])])]),t._v(" "),a("div",{staticClass:"tile is-parent is-4"},[a("article",{staticClass:"tile is-child box"},[a("h1",{staticClass:"title"},[t._v("Card")]),t._v(" "),a("a",{staticClass:"button is-primary is-large modal-button",on:{click:function(e){t.openModalCard()}}},[t._v("Launch modal card")])])])]),t._v(" "),a("modal",{attrs:{visible:t.showModal},on:{close:t.closeModalBasic}})],1)},staticRenderFns:[]}},660:function(t,e,a){var i=a(0)(a(876),a(884),null,null);t.exports=i.exports},662:function(t,e,a){a(1012);var i=a(0)(a(932),a(1019),null,null);t.exports=i.exports},876:function(t,e,a){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var i=a(19);e.default={components:{CardModal:i.c},props:{visible:Boolean,title:String,url:String},data:function(){return{src:a(41)}},methods:{open:function(t){window.open(t)},close:function(){this.$emit("close")}}}},884:function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("card-modal",{attrs:{visible:t.visible,title:t.title,transition:"zoom"},on:{close:t.close}},[a("div",{staticClass:"content has-text-centered"},[a("img",{attrs:{src:t.src,height:"120",alt:"Vue Admin"}})]),t._v(" "),a("a",{on:{click:function(e){t.open(t.url)}}},[t._v("Vue Admin on GitHub")])])},staticRenderFns:[]}},932:function(t,e,a){"use strict";Object.defineProperty(e,"__esModule",{value:!0});var i=a(2),s=a(75),o=a.n(s),n=a(74),l=a.n(n),c=a(660),r=a.n(c),d=i.default.extend(l.a),u=i.default.extend(r.a),v=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{visible:!0};return new d({el:document.createElement("div"),propsData:t})},p=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{visible:!0};return new u({el:document.createElement("div"),propsData:t})};e.default={components:{Modal:o.a},data:function(){return{showModal:!0,cardModal:null,imageModal:null}},methods:{openModalBasic:function(){this.showModal=!0},closeModalBasic:function(){this.showModal=!1},openModalImage:function(){(this.imageModal||(this.imageModal=v())).$children[0].active()},openModalCard:function(){(this.cardModal||(this.cardModal=p({title:"Modal title",url:this.$store.state.pkg.homepage}))).$children[0].active()}}}},941:function(t,e,a){e=t.exports=a(658)(!0),e.push([t.i,"","",{version:3,sources:[],names:[],mappings:"",file:"Modal.vue",sourceRoot:""}])}}); 2 | //# sourceMappingURL=4.23fab30cde5ebb4b6a96.js.map -------------------------------------------------------------------------------- /frontend/dist/assets/js/5.c760f725e308a0951f63.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([5],{1008:function(t,e,a){var s=a(937);"string"==typeof s&&(s=[[t.i,s,""]]),s.locals&&(t.exports=s.locals);a(659)("794d9428",s,!0)},1015:function(t,e){t.exports={render:function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",[a("div",{staticClass:"bar"},[a("router-link",{staticClass:"is-3",attrs:{to:{path:"/template/create"}}},[a("button",{staticClass:"button is-primary"},[t._v("添加")])])],1),t._v(" "),t._l(t.templatesData.res_templates,function(e,s){return s%3==0?a("div",{staticClass:"tile is-ancestor"},[s\n \n
\"Vue
\n Vue Admin on GitHub\n
\n\n\n\n\n\n\n// WEBPACK FOOTER //\n// CardModal.vue?c6239756","module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;\n return _c('card-modal', {\n attrs: {\n \"visible\": _vm.visible,\n \"title\": _vm.title,\n \"transition\": \"zoom\"\n },\n on: {\n \"close\": _vm.close\n }\n }, [_c('div', {\n staticClass: \"content has-text-centered\"\n }, [_c('img', {\n attrs: {\n \"src\": _vm.src,\n \"height\": \"120\",\n \"alt\": \"Vue Admin\"\n }\n })]), _vm._v(\" \"), _c('a', {\n on: {\n \"click\": function($event) {\n _vm.open(_vm.url)\n }\n }\n }, [_vm._v(\"Vue Admin on GitHub\")])])\n},staticRenderFns: []}\n\n\n//////////////////\n// WEBPACK FOOTER\n// ./~/vue-loader/lib/template-compiler?{\"id\":\"data-v-31e8c3a7\"}!./~/vue-loader/lib/selector.js?type=template&index=0!./client/views/components/modals/CardModal.vue\n// module id = 884\n// module chunks = 4 6"],"sourceRoot":""} -------------------------------------------------------------------------------- /frontend/dist/assets/js/manifest.228aafaf19fdc45611fe.js: -------------------------------------------------------------------------------- 1 | !function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,a,c){for(var f,i,u,s=0,d=[];sSparrow
-------------------------------------------------------------------------------- /frontend/dist/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/frontend/dist/logo.png -------------------------------------------------------------------------------- /frontend/electronIndex.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { app, BrowserWindow } = require('electron') 4 | 5 | // Keep a global reference of the window object, if you don't, the window will 6 | // be closed automatically when the JavaScript object is garbage collected. 7 | let mainWindow 8 | 9 | const isDev = process.env.NODE_ENV === 'development' 10 | 11 | function createWindow () { 12 | // Create the browser window. 13 | mainWindow = new BrowserWindow({ 14 | width: 800, 15 | height: 600 16 | }) 17 | 18 | // and load the index.html of the app. 19 | mainWindow.loadURL(`file://${__dirname}/dist/index.html`) 20 | 21 | // Open the DevTools. 22 | if (isDev) { 23 | mainWindow.webContents.openDevTools() 24 | 25 | const installExtension = require('electron-devtools-installer') 26 | installExtension.default(installExtension.VUEJS_DEVTOOLS) 27 | .then(name => console.log(`Added Extension: ${name}`)) 28 | .catch(err => console.log('An error occurred: ', err)) 29 | } 30 | 31 | // Emitted when the window is closed. 32 | mainWindow.on('closed', () => { 33 | // Dereference the window object, usually you would store windows 34 | // in an array if your app supports multi windows, this is the time 35 | // when you should delete the corresponding element. 36 | mainWindow = null 37 | }) 38 | } 39 | 40 | // This method will be called when Electron has finished 41 | // initialization and is ready to create browser windows. 42 | // Some APIs can only be used after this event occurs. 43 | app.on('ready', createWindow) 44 | 45 | // Quit when all windows are closed. 46 | app.on('window-all-closed', () => { 47 | // On OS X it is common for applications and their menu bar 48 | // to stay active until the user quits explicitly with Cmd + Q 49 | if (process.platform !== 'darwin') { 50 | app.quit() 51 | } 52 | }) 53 | 54 | app.on('activate', () => { 55 | // On OS X it's common to re-create a window in the app when the 56 | // dock icon is clicked and there are no other windows open. 57 | if (mainWindow === null) { 58 | createWindow() 59 | } 60 | }) 61 | 62 | // In this file you can include the rest of your app's specific main process 63 | // code. You can also put them in separate files and require them here. 64 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <%= htmlWebpackPlugin.options.title %> 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sparrow", 3 | "version": "1.4.1", 4 | "description": "Tools for the Mock API", 5 | "repository": "eleme/Sparrow", 6 | "homepage": "https://github.com/eleme/Sparrow", 7 | "author": { 8 | "name": "小鱼周凌宇", 9 | "email": "coderfish@163.com", 10 | "url": "zhoulingyu.com" 11 | }, 12 | "keywords": [ 13 | "mock", 14 | "api", 15 | "vue" 16 | ], 17 | "engines": { 18 | "node": ">=4", 19 | "npm": ">=3" 20 | }, 21 | "scripts": { 22 | "build": "cross-env NODE_ENV=production node build/build.js", 23 | "clean": "rm -rf dist", 24 | "dev": "cross-env NODE_ENV=development node build/dev-server.js", 25 | "electron": "cross-env NODE_ELECTRON=true npm run build && electron electronIndex.js", 26 | "gh": "npm run build && gh-pages -d dist", 27 | "lint": "eslint --ext .js .vue client/*", 28 | "lint:fix": "eslint --fix --ext .js .vue electron.js client/* build/* config/*", 29 | "test": "echo lol" 30 | }, 31 | "dependencies": { 32 | "@websanova/vue-auth": "^2.8.2-beta", 33 | "animate.css": "3.5.2", 34 | "animejs": "^2.0.1", 35 | "axios": "^0.15.3", 36 | "bulma": "^0.3.2", 37 | "echarts": "^4.1.0", 38 | "font-awesome": "4.7.0", 39 | "mdi": "^1.8.36", 40 | "plotly.js": "^1.33.0", 41 | "qrcode": "1.2.0", 42 | "v-charts": "^1.16.20", 43 | "vue": "^2.2.2", 44 | "vue-axios": "^2.0.1", 45 | "vue-bulma-brace": "^0.1.0", 46 | "vue-bulma-breadcrumb": "^1.0.1", 47 | "vue-bulma-card": "^1.0.2", 48 | "vue-bulma-chartist": "^1.1.0", 49 | "vue-bulma-chartjs": "^1.0.4", 50 | "vue-bulma-collapse": "1.0.3", 51 | "vue-bulma-datepicker": "^1.3.0", 52 | "vue-bulma-emoji": "^0.0.2", 53 | "vue-bulma-expanding": "^0.0.1", 54 | "vue-bulma-jump": "^0.0.2", 55 | "vue-bulma-message": "^1.1.1", 56 | "vue-bulma-modal": "1.0.1", 57 | "vue-bulma-notification": "^1.1.1", 58 | "vue-bulma-progress-bar": "^1.0.2", 59 | "vue-bulma-progress-tracker": "0.0.4", 60 | "vue-bulma-quill": "0.0.1", 61 | "vue-bulma-rating": "^1.0.1", 62 | "vue-bulma-slider": "^1.0.2", 63 | "vue-bulma-switch": "^1.0.4", 64 | "vue-bulma-tabs": "^1.1.2", 65 | "vue-bulma-tooltip": "^1.0.3", 66 | "vue-cleave": "1.1.1", 67 | "vue-highlightjs": "^1.3.3", 68 | "vue-lory": "0.0.4", 69 | "vue-nprogress": "0.1.5", 70 | "vue-peity": "0.5.0", 71 | "vue-resource": "^1.3.5", 72 | "vue-router": "^2.3.0", 73 | "vuex": "^2.2.1", 74 | "vuex-router-sync": "^4.1.2", 75 | "wysiwyg.css": "0.0.2" 76 | }, 77 | "devDependencies": { 78 | "autoprefixer": "^6.7.7", 79 | "babel-core": "^6.21.0", 80 | "babel-eslint": "^7.1.1", 81 | "babel-loader": "^6.4.0", 82 | "babel-plugin-transform-export-extensions": "^6.8.0", 83 | "babel-preset-es2015": "^6.14.0", 84 | "babel-preset-stage-2": "^6.17.0", 85 | "connect-history-api-fallback": "^1.3.0", 86 | "cross-env": "^3.1.3", 87 | "css-loader": "^0.27.1", 88 | "electron-devtools-installer": "^2.0.1", 89 | "element-ui": "^2.3.2", 90 | "eslint": "^3.17.1", 91 | "eslint-config-standard": "^7.0.1", 92 | "eslint-friendly-formatter": "^2.0.7", 93 | "eslint-loader": "^1.6.1", 94 | "eslint-plugin-html": "^2.0.1", 95 | "eslint-plugin-promise": "^3.5.0", 96 | "eslint-plugin-standard": "^2.1.1", 97 | "eventsource-polyfill": "^0.9.6", 98 | "express": "^4.15.2", 99 | "extract-text-webpack-plugin": "^2.0.0-beta.4", 100 | "file-loader": "^0.10.1", 101 | "html-webpack-plugin": "^2.25.0", 102 | "http-proxy-middleware": "^0.17.4", 103 | "imports-loader": "^0.7.0", 104 | "jsoneditor": "^5.13.3", 105 | "node-sass": "^4.1.1", 106 | "opn": "^4.0.2", 107 | "ora": "^1.1.0", 108 | "postcss-loader": "^1.3.3", 109 | "progress-bar-webpack-plugin": "^1.9.1", 110 | "sass-loader": "^6.0.3", 111 | "serve-favicon": "^2.4.1", 112 | "style-loader": "^0.13.1", 113 | "stylus": "^0.54.5", 114 | "stylus-loader": "^3.0.1", 115 | "url-loader": "^0.5.7", 116 | "vue-cookie": "^1.1.4", 117 | "vue-html-loader": "^1.2.3", 118 | "vue-loader": "^11.1.4", 119 | "vue-template-compiler": "^2.2.2", 120 | "webpack": "^2.2.1", 121 | "webpack-dev-middleware": "^1.9.0", 122 | "webpack-hot-middleware": "^2.14.0", 123 | "webpack-merge": "^4.0.0" 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/log/.gitkeep -------------------------------------------------------------------------------- /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", "Sparrow.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrs==17.4.0 2 | certifi==2017.11.5 3 | chardet==3.0.4 4 | Django==2.0 5 | django-cors-headers==2.1.0 6 | django-guardian==1.4.9 7 | djangorestframework==3.7.7 8 | djangorestframework-jwt==1.11.0 9 | future==0.16.0 10 | idna==2.6 11 | jsonpickle==0.9.5 12 | pep8==1.7.1 13 | PyJWT==1.6.1 14 | pytz==2017.3 15 | PyYAML==3.12 16 | qrcode==6.0 17 | related==0.5.3 18 | requests==2.18.4 19 | singledispatch==3.4.0.3 20 | six==1.11.0 21 | urllib3==1.22 22 | -------------------------------------------------------------------------------- /res/Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/res/Banner.png -------------------------------------------------------------------------------- /res/data-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/res/data-01.png -------------------------------------------------------------------------------- /res/data-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/res/data-03.png -------------------------------------------------------------------------------- /res/data-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eleme/Sparrow/4fe10cb57c40dbdca9fe3645477cb7e1dc118d0b/res/data-04.png --------------------------------------------------------------------------------