├── _config.yml ├── LANGS.md ├── api ├── src │ ├── mail │ │ ├── templates │ │ │ ├── accept_invitation_cn.html │ │ │ ├── reject_invitation_cn.html │ │ │ ├── find_pwd_cn.html │ │ │ ├── user_info_cn.html │ │ │ ├── accept_invitation_en.html │ │ │ ├── reject_invitation_en.html │ │ │ ├── find_pwd_en.html │ │ │ └── user_info_en.html │ │ └── template_setting.ts │ ├── global_data │ │ └── data │ │ │ └── test.xlsx │ ├── interfaces │ │ ├── reg_token.ts │ │ ├── res_object.ts │ │ ├── invite_project_token.ts │ │ ├── user_data.ts │ │ └── stress_case_info.ts │ ├── run_engine │ │ └── process │ │ │ ├── stress_nodejs_process_handler.ts │ │ │ ├── base_process_handler.ts │ │ │ ├── schedule_process_handler.ts │ │ │ ├── stress_nodejs_worker_handler.ts │ │ │ ├── schedule_process.ts │ │ │ └── stress_process_handler.ts │ ├── models │ │ ├── stress_failed_info.ts │ │ ├── localhost_mapping.ts │ │ ├── stress_record.ts │ │ ├── record_doc_history.ts │ │ ├── variable.ts │ │ ├── record_history.ts │ │ ├── user.ts │ │ ├── query_string.ts │ │ ├── body_form_data.ts │ │ ├── record_doc.ts │ │ ├── environment.ts │ │ ├── header.ts │ │ ├── schedule_record.ts │ │ ├── mock_collection.ts │ │ ├── stress.ts │ │ └── project.ts │ ├── middlewares │ │ ├── route_failed.ts │ │ ├── async_init.ts │ │ ├── error_handle.ts │ │ ├── session_handle.ts │ │ └── middleware.ts │ ├── utils │ │ ├── message.ts │ │ ├── log.ts │ │ └── func_util.ts │ ├── services │ │ ├── base │ │ │ ├── web_socket_handler.ts │ │ │ └── request_import.ts │ │ ├── console_message.ts │ │ ├── schedule_on_demand_service.ts │ │ ├── web_socket_service.ts │ │ ├── header_service.ts │ │ ├── form_data_service.ts │ │ ├── query_string_service.ts │ │ ├── user_variable_manager.ts │ │ └── sample_service.ts │ ├── index.ts │ ├── controllers │ │ ├── environment_controller.ts │ │ ├── stress_controller.ts │ │ ├── mock_api_controller.ts │ │ └── sample_controller.ts │ └── setup.ts ├── pm2.json ├── tsconfig.json ├── logconfig.json └── typings.json ├── test case.xlsx ├── doc └── images │ ├── env.png │ ├── header.gif │ ├── history.png │ ├── schedule.png │ └── collection.png ├── client ├── src │ ├── common │ │ ├── link.bat │ │ ├── enum │ │ │ ├── mock_mode.ts │ │ │ ├── record_category.ts │ │ │ ├── metadata_type.ts │ │ │ ├── data_mode.ts │ │ │ ├── string_type.ts │ │ │ ├── parameter_type.ts │ │ │ ├── period.ts │ │ │ ├── stress_type.ts │ │ │ └── notification_mode.ts │ │ ├── interfaces │ │ │ ├── dto_error.ts │ │ │ ├── password.ts │ │ │ ├── dto_project_quit.ts │ │ │ ├── dto_user.ts │ │ │ ├── dto_record_run.ts │ │ │ ├── dto_record_sort.ts │ │ │ ├── dto_assert.ts │ │ │ ├── dto_mock.ts │ │ │ ├── dto_stress_record.ts │ │ │ ├── dto_header.ts │ │ │ ├── dto_environment.ts │ │ │ ├── dto_variable.ts │ │ │ ├── dto_project_data.ts │ │ │ ├── dto_schedule_record.ts │ │ │ ├── dto_mock_collection.ts │ │ │ ├── dto_mail.ts │ │ │ ├── dto_res.ts │ │ │ ├── dto_project.ts │ │ │ ├── dto_stress.ts │ │ │ ├── dto_run_result.ts │ │ │ ├── dto_collection.ts │ │ │ ├── dto_schedule.ts │ │ │ ├── postman_v1.ts │ │ │ └── dto_record.ts │ │ ├── link.sh │ │ └── utils │ │ │ ├── math_util.ts │ │ │ └── date_util.ts │ ├── components │ │ ├── splitter │ │ │ ├── style │ │ │ │ └── index.less │ │ │ └── index.tsx │ │ ├── diff_dialog │ │ │ ├── style │ │ │ │ └── index.less │ │ │ └── index.tsx │ │ ├── name_with_tag │ │ │ ├── style │ │ │ │ └── index.less │ │ │ └── index.tsx │ │ ├── sortable_list │ │ │ └── style │ │ │ │ └── index.less │ │ ├── tab_dot │ │ │ ├── style │ │ │ │ └── index.less │ │ │ └── index.tsx │ │ ├── code_snippet_dialog │ │ │ └── style │ │ │ │ └── index.less │ │ ├── font_icon │ │ │ ├── style │ │ │ │ └── index.less │ │ │ ├── font_icon.tsx │ │ │ └── http_method_icon.tsx │ │ ├── sider_layout │ │ │ └── styles │ │ │ │ └── index.less │ │ ├── validated_name │ │ │ ├── style │ │ │ │ └── index.less │ │ │ └── index.tsx │ │ ├── confirm_dialog │ │ │ └── index.ts │ │ ├── record_timeline │ │ │ └── style │ │ │ │ └── index.less │ │ ├── res_error_panel │ │ │ ├── style │ │ │ │ └── index.less │ │ │ └── index.tsx │ │ ├── environment_select │ │ │ └── style │ │ │ │ └── index.less │ │ ├── editor │ │ │ └── style │ │ │ │ └── index.less │ │ ├── assert_json_view │ │ │ └── assert_funcs.ts │ │ ├── editable_cell │ │ │ └── style │ │ │ │ └── index.less │ │ ├── indicator │ │ │ ├── style │ │ │ │ └── index.less │ │ │ └── index.tsx │ │ ├── highlight_code │ │ │ └── index.tsx │ │ ├── key_value │ │ │ └── style │ │ │ │ └── index.less │ │ └── script_dialog │ │ │ └── index.tsx │ ├── misc │ │ ├── conflict_type.ts │ │ ├── record_category.ts │ │ ├── tab_action.ts │ │ ├── key_value_pair.ts │ │ ├── request_status.ts │ │ ├── body_type.ts │ │ ├── parameter_type.ts │ │ ├── http_method.ts │ │ ├── schedule_statistics.ts │ │ ├── stress_type.ts │ │ ├── code_snippet_type.ts │ │ ├── test_snippet.ts │ │ └── notification_mode.ts │ ├── style │ │ ├── fonts │ │ │ ├── iconfont.eot │ │ │ ├── iconfont.ttf │ │ │ ├── iconfont.woff │ │ │ ├── SourceCodePro-Bold.woff │ │ │ ├── SourceCodePro-Medium.woff │ │ │ ├── SourceCodePro-Regular.woff │ │ │ └── SourceCodePro-Semibold.woff │ │ ├── theme.tsx │ │ └── colors.less │ ├── locales │ │ ├── index.tsx │ │ ├── lang.ts │ │ ├── input.tsx │ │ └── string.ts │ ├── state │ │ ├── request.ts │ │ ├── local_data.ts │ │ ├── environment.ts │ │ ├── project.ts │ │ ├── document.ts │ │ ├── stress.ts │ │ ├── schedule.ts │ │ ├── user.ts │ │ └── index.ts │ ├── utils │ │ ├── urls.ts │ │ ├── global_var.ts │ │ ├── local_store.ts │ │ ├── date_util.ts │ │ ├── compare_util.ts │ │ └── template_util.ts │ ├── action │ │ ├── document.ts │ │ ├── ui.ts │ │ └── local_data.ts │ ├── index.tsx │ ├── reducer │ │ ├── local_data.ts │ │ ├── document.ts │ │ └── __tests__ │ │ │ └── local_data.test.ts │ ├── modules │ │ ├── login │ │ │ ├── style │ │ │ │ └── index.less │ │ │ └── loading_screen.tsx │ │ ├── project │ │ │ └── style │ │ │ │ └── index.less │ │ ├── api_mock │ │ │ └── index.tsx │ │ ├── header │ │ │ └── style │ │ │ │ └── index.less │ │ └── stress_test │ │ │ └── style │ │ │ └── index.less │ └── store.ts ├── public │ ├── favicon.ico │ ├── hitchhiker-title-dark.png │ ├── index.html │ └── puff.svg ├── .babelrc ├── .gitignore ├── config │ ├── postcss.config.js │ ├── jest │ │ ├── fileTransform.js │ │ ├── cssTransform.js │ │ └── typescriptTransform.js │ ├── polyfills.js │ └── env.js ├── debugwebpack.js ├── scripts │ └── test.js └── tsconfig.json ├── cn ├── TODO.md ├── import.md ├── Script │ ├── Global_Func.md │ ├── Pre_Script.md │ ├── custom-data-file.md │ ├── custom-javascript-lib.md │ ├── Common_Pre_Script.md │ ├── Test.md │ └── README.md ├── other.md ├── Schedule │ ├── README.md │ └── Create_Schedule.md ├── Variable │ ├── README.md │ ├── Dynamic_Var.md │ ├── Env_Var.md │ └── Param_Var.md ├── installation │ └── README.md ├── Stress │ ├── Node.md │ ├── Create_Stress.md │ ├── Run.md │ └── README.md ├── Simple_Tutorial │ ├── Assert_Base_On_UI.md │ ├── README.md │ ├── Create_Collection.md │ ├── Create_Project.md │ └── Use_Env_Var.md └── SUMMARY.md ├── en ├── TODO.md ├── import.md ├── Script │ ├── Global_Func.md │ ├── Pre_Script.md │ ├── Common_Pre_Script.md │ ├── custom-javascript-lib.md │ ├── custom-data-file.md │ ├── Test.md │ └── README.md ├── other.md ├── Schedule │ ├── README.md │ └── Create_Schedule.md ├── Simple_Tutorial │ ├── Assert_Base_On_UI.md │ ├── README.md │ ├── Create_Collection.md │ └── Create_Project.md ├── Stress │ ├── Node.md │ ├── README.md │ ├── Create_Stress.md │ └── Run.md ├── installation │ ├── README.md │ ├── linux.md │ └── StepByStep.md └── Variable │ ├── Dynamic_Var.md │ ├── README.md │ ├── Env_Var.md │ └── Param_Var.md ├── deploy ├── docker │ ├── hitchhiker_and_mysql │ │ ├── hitchhiker-mysql.cnf │ │ └── docker-compose.yml │ ├── mysql │ │ └── docker-compose.yml │ ├── nginx │ │ └── docker-compose.yml │ ├── push_docker.sh │ ├── private │ │ ├── update.sh │ │ └── docker-compose.yml │ ├── hitchhiker │ │ └── docker-compose.yml │ └── Dockerfile ├── package.bat ├── linux_deploy.sh └── win_deploy.bat ├── .vscode ├── settings.json └── tasks.json ├── .github └── ISSUE_TEMPLATE │ ├── feature-request.md │ ├── bug-report.md │ └── security-issue-report.md └── .gitignore /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /LANGS.md: -------------------------------------------------------------------------------- 1 | # Languages 2 | 3 | * [中文版](cn/) 4 | * [English](en/) -------------------------------------------------------------------------------- /api/src/mail/templates/accept_invitation_cn.html: -------------------------------------------------------------------------------- 1 | {{user}} 接受了您的邀请,加入 {{project}} 项目。 -------------------------------------------------------------------------------- /api/src/mail/templates/reject_invitation_cn.html: -------------------------------------------------------------------------------- 1 | {{user}} 拒绝了您的邀请,没有加入{{project}}项目。 -------------------------------------------------------------------------------- /test case.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/test case.xlsx -------------------------------------------------------------------------------- /api/src/mail/templates/find_pwd_cn.html: -------------------------------------------------------------------------------- 1 | 您已重置您的 [HitchhikerAPI] 密码,新密码为:{{pwd}} ,请尽快登录并修改密码。 -------------------------------------------------------------------------------- /doc/images/env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/doc/images/env.png -------------------------------------------------------------------------------- /client/src/common/link.bat: -------------------------------------------------------------------------------- 1 | mklink /D "%~dp0..\..\..\api\src\common" "%~dp0..\common" 2 | pause -------------------------------------------------------------------------------- /doc/images/header.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/doc/images/header.gif -------------------------------------------------------------------------------- /api/src/mail/templates/user_info_cn.html: -------------------------------------------------------------------------------- 1 | 成功加入 {{project}} 项目,您可以用您的email为用户名,密码:{{password}} 来登录系统,欢迎使用。 -------------------------------------------------------------------------------- /doc/images/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/doc/images/history.png -------------------------------------------------------------------------------- /doc/images/schedule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/doc/images/schedule.png -------------------------------------------------------------------------------- /api/src/mail/templates/accept_invitation_en.html: -------------------------------------------------------------------------------- 1 | {{user}} accept your invitation to join {{project}} project. -------------------------------------------------------------------------------- /api/src/mail/templates/reject_invitation_en.html: -------------------------------------------------------------------------------- 1 | {{user}} reject your invitation to join {{project}} project. -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /doc/images/collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/doc/images/collection.png -------------------------------------------------------------------------------- /api/src/global_data/data/test.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/api/src/global_data/data/test.xlsx -------------------------------------------------------------------------------- /client/src/common/enum/mock_mode.ts: -------------------------------------------------------------------------------- 1 | export enum MockMode { 2 | 3 | template = 0, 4 | 5 | nativelData = 1 6 | } -------------------------------------------------------------------------------- /client/src/common/enum/record_category.ts: -------------------------------------------------------------------------------- 1 | export enum RecordCategory { 2 | 3 | folder = 10, 4 | 5 | record = 20, 6 | } -------------------------------------------------------------------------------- /client/src/components/splitter/style/index.less: -------------------------------------------------------------------------------- 1 | .splitter { 2 | width: 5px; 3 | height: 100%; 4 | cursor: ew-resize; 5 | } -------------------------------------------------------------------------------- /client/src/misc/conflict_type.ts: -------------------------------------------------------------------------------- 1 | export enum ConflictType { 2 | 3 | none, 4 | 5 | modify, 6 | 7 | delete 8 | } -------------------------------------------------------------------------------- /client/src/misc/record_category.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum RecordCategory { 3 | 4 | folder = 10, 5 | 6 | record = 20 7 | } -------------------------------------------------------------------------------- /client/src/style/fonts/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/client/src/style/fonts/iconfont.eot -------------------------------------------------------------------------------- /client/src/style/fonts/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/client/src/style/fonts/iconfont.ttf -------------------------------------------------------------------------------- /client/src/style/fonts/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/client/src/style/fonts/iconfont.woff -------------------------------------------------------------------------------- /client/public/hitchhiker-title-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/client/public/hitchhiker-title-dark.png -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_error.ts: -------------------------------------------------------------------------------- 1 | export interface DtoError { 2 | 3 | message: string; 4 | 5 | stack: string; 6 | } -------------------------------------------------------------------------------- /api/src/mail/templates/find_pwd_en.html: -------------------------------------------------------------------------------- 1 | you reset your [HitchhikerAPI] password,new password is:{{pwd}} ,please login and change password。 -------------------------------------------------------------------------------- /api/src/interfaces/reg_token.ts: -------------------------------------------------------------------------------- 1 | export interface RegToken { 2 | 3 | host: string; 4 | 5 | date: Date; 6 | 7 | uid: string; 8 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/password.ts: -------------------------------------------------------------------------------- 1 | export interface Password { 2 | 3 | oldPassword: string; 4 | 5 | newPassword: string; 6 | } -------------------------------------------------------------------------------- /client/src/common/link.sh: -------------------------------------------------------------------------------- 1 | work_path=$(dirname $(readlink -f $0)) 2 | ln -sf "${work_path}..\..\..\api\src\common" "${work_path}..\common" 3 | -------------------------------------------------------------------------------- /client/src/style/theme.tsx: -------------------------------------------------------------------------------- 1 | export const primaryColor = '#108ee9'; 2 | 3 | export const normalBadgeStyle = { backgroundColor: primaryColor }; -------------------------------------------------------------------------------- /api/src/mail/templates/user_info_en.html: -------------------------------------------------------------------------------- 1 | You had join {{project}} project, you can use your email as account and password: {{password}} to login. -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_project_quit.ts: -------------------------------------------------------------------------------- 1 | export class DtoProjectQuit { 2 | 3 | projectId: string; 4 | 5 | userId: string; 6 | } -------------------------------------------------------------------------------- /client/src/style/fonts/SourceCodePro-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/client/src/style/fonts/SourceCodePro-Bold.woff -------------------------------------------------------------------------------- /client/src/components/diff_dialog/style/index.less: -------------------------------------------------------------------------------- 1 | 2 | .d2h-file-name-wrapper{ 3 | .d2h-tag{ 4 | display: none; 5 | } 6 | } 7 | 8 | -------------------------------------------------------------------------------- /client/src/style/fonts/SourceCodePro-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/client/src/style/fonts/SourceCodePro-Medium.woff -------------------------------------------------------------------------------- /client/src/style/fonts/SourceCodePro-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/client/src/style/fonts/SourceCodePro-Regular.woff -------------------------------------------------------------------------------- /cn/TODO.md: -------------------------------------------------------------------------------- 1 | 参考 2 | 3 | [issues](https://github.com/brookshi/Hitchhiker/issues) 4 | 5 | [milestones](https://github.com/brookshi/Hitchhiker/milestones) -------------------------------------------------------------------------------- /api/src/interfaces/res_object.ts: -------------------------------------------------------------------------------- 1 | export interface ResObject { 2 | 3 | message: string; 4 | 5 | success: boolean; 6 | 7 | result?: any; 8 | } -------------------------------------------------------------------------------- /client/src/style/fonts/SourceCodePro-Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brookshi/Hitchhiker/HEAD/client/src/style/fonts/SourceCodePro-Semibold.woff -------------------------------------------------------------------------------- /en/TODO.md: -------------------------------------------------------------------------------- 1 | Refer to 2 | 3 | [issues](https://github.com/brookshi/Hitchhiker/issues) 4 | 5 | [milestones](https://github.com/brookshi/Hitchhiker/milestones) -------------------------------------------------------------------------------- /client/src/common/enum/metadata_type.ts: -------------------------------------------------------------------------------- 1 | export enum MetadataType { 2 | 3 | PostmanAllV1, 4 | 5 | PostmanCollectionV1, 6 | 7 | PostmanCollectionV2 8 | } -------------------------------------------------------------------------------- /client/src/common/enum/data_mode.ts: -------------------------------------------------------------------------------- 1 | export enum DataMode { 2 | 3 | urlencoded = 0, 4 | 5 | raw = 1, 6 | 7 | form = 2, 8 | 9 | binary = 3 10 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_user.ts: -------------------------------------------------------------------------------- 1 | export interface DtoUser { 2 | 3 | id: string; 4 | 5 | name: string; 6 | 7 | email: string; 8 | 9 | password: string; 10 | } -------------------------------------------------------------------------------- /client/src/misc/tab_action.ts: -------------------------------------------------------------------------------- 1 | export enum TabAction { 2 | 3 | none, 4 | 5 | saveAll, 6 | 7 | closeUnmodified, 8 | 9 | closeOthers, 10 | 11 | closeAll 12 | } -------------------------------------------------------------------------------- /client/src/common/enum/string_type.ts: -------------------------------------------------------------------------------- 1 | export type BodyType = 'json' | 'xml' | 'text'; 2 | 3 | export type ProjectFolderType = 'lib' | 'data'; 4 | 5 | export type ImportType = 'swagger' | 'postman'; -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_record_run.ts: -------------------------------------------------------------------------------- 1 | import { DtoRecord } from './dto_record'; 2 | 3 | export interface DtoRecordRun { 4 | 5 | environment: string; 6 | 7 | record: DtoRecord; 8 | } -------------------------------------------------------------------------------- /client/src/misc/key_value_pair.ts: -------------------------------------------------------------------------------- 1 | export interface KeyValuePair { 2 | 3 | key: string; 4 | 5 | value?: string; 6 | 7 | isActive: boolean; 8 | 9 | description?: string; 10 | } -------------------------------------------------------------------------------- /client/src/misc/request_status.ts: -------------------------------------------------------------------------------- 1 | export enum RequestStatus { 2 | 3 | none = 0, 4 | 5 | pending = 1, 6 | 7 | success = 2, 8 | 9 | failed = 3, 10 | 11 | cancel = 4 12 | } -------------------------------------------------------------------------------- /cn/import.md: -------------------------------------------------------------------------------- 1 | #### 从Postman导入数据 2 | 3 | Hitchhiker支持Postman的V1数据和Swagger的v2数据,你可以通过如图的方式把这个json文件导入到Hitchhiker里: 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/import_postman_v1.png) -------------------------------------------------------------------------------- /deploy/docker/hitchhiker_and_mysql/hitchhiker-mysql.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | collation-server=utf8_general_ci 3 | init-connect='SET NAMES utf8' 4 | character-set-server=utf8 5 | max_allowed_packet=200M 6 | max_connections=1024 -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_record_sort.ts: -------------------------------------------------------------------------------- 1 | export interface DtoRecordSort { 2 | 3 | recordId: string; 4 | 5 | folderId: string; 6 | 7 | collectionId: string; 8 | 9 | newSort: number; 10 | } -------------------------------------------------------------------------------- /client/src/components/name_with_tag/style/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | .number-tag-normal { 3 | color: @primary-color; 4 | } 5 | 6 | .number-tag-warning { 7 | color: @warning-color; 8 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_assert.ts: -------------------------------------------------------------------------------- 1 | export interface DtoAssert { 2 | 3 | name: string; 4 | 5 | env?: string; 6 | 7 | target: string[]; 8 | 9 | function: string; 10 | 11 | value: string; 12 | } -------------------------------------------------------------------------------- /api/pm2.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [{ 3 | "name": "hitchhiker", 4 | "script": "./build/index.js", 5 | "watch": false, 6 | "env": { 7 | "HITCHHIKER_APP_HOST": "myhost" 8 | } 9 | }] 10 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_mock.ts: -------------------------------------------------------------------------------- 1 | import { MockMode } from '../enum/mock_mode'; 2 | import { DtoBaseItem } from './dto_record'; 3 | 4 | export interface DtoMock extends DtoBaseItem { 5 | 6 | mode: MockMode; 7 | 8 | res?: string; 9 | } -------------------------------------------------------------------------------- /deploy/docker/mysql/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | mysql: 4 | image: mysql:5.7 5 | container_name: hitchhiker-mysql 6 | environment: 7 | - MYSQL_ROOT_PASSWORD=hitchhiker888 8 | - MYSQL_DATABASE=hitchhiker-prod -------------------------------------------------------------------------------- /client/src/misc/body_type.ts: -------------------------------------------------------------------------------- 1 | export const bodyTypes = { 2 | 'application/json': 'json', 3 | 'application/javascript': 'json', 4 | 'application/xml': 'xml', 5 | 'text/xml': 'xml', 6 | 'text/plain': 'text', 7 | 'text/html': 'xml' 8 | }; -------------------------------------------------------------------------------- /en/import.md: -------------------------------------------------------------------------------- 1 | #### Import from Postman v1 data 2 | 3 | Hitchhiker support Postman v1 data and Swagger V2 data, you can json file to Hitchhiker as below: 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/import_postman_v1.png) -------------------------------------------------------------------------------- /client/src/locales/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormattedMessage } from 'react-intl'; 3 | 4 | export default function (msgID: string, values?: any) { 5 | return ( 6 | 7 | ); 8 | } -------------------------------------------------------------------------------- /client/src/misc/parameter_type.ts: -------------------------------------------------------------------------------- 1 | export enum ParameterType { 2 | 3 | ManyToMany = 0, 4 | 5 | OneToOne = 1 6 | } 7 | 8 | export enum ReduceAlgorithmType { 9 | 10 | none = 0, 11 | 12 | pairwise = 1, 13 | 14 | orthogonalArray = 2 15 | } -------------------------------------------------------------------------------- /client/src/components/sortable_list/style/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | 3 | .sortable-dragicon { 4 | margin-right: 4px; 5 | font-size: 13px; 6 | cursor: move; 7 | &:hover { 8 | color: @primary-color; 9 | } 10 | } -------------------------------------------------------------------------------- /api/src/run_engine/process/stress_nodejs_process_handler.ts: -------------------------------------------------------------------------------- 1 | import { BaseProcessHandler } from './base_process_handler'; 2 | 3 | export class StressNodejsProcessHandler extends BaseProcessHandler { 4 | 5 | handleMessage() { } 6 | 7 | afterProcessCreated() { } 8 | } -------------------------------------------------------------------------------- /client/src/common/enum/parameter_type.ts: -------------------------------------------------------------------------------- 1 | export enum ParameterType { 2 | 3 | ManyToMany = 0, 4 | 5 | OneToOne = 1 6 | } 7 | 8 | export enum ReduceAlgorithmType { 9 | 10 | none = 0, 11 | 12 | pairwise = 1, 13 | 14 | orthogonalArray = 2 15 | } -------------------------------------------------------------------------------- /deploy/package.bat: -------------------------------------------------------------------------------- 1 | set NODE_ENV=develop 2 | git clone -b release https://github.com/brookshi/Hitchhiker.git 3 | cd hitchhiker 4 | call npm install 5 | cd client 6 | call npm install 7 | cd.. 8 | 9 | set NODE_ENV=production 10 | call gulp package --prod 11 | 12 | pause -------------------------------------------------------------------------------- /api/src/interfaces/invite_project_token.ts: -------------------------------------------------------------------------------- 1 | export interface InviteToProjectToken { 2 | 3 | userEmail: string; 4 | 5 | inviterId: string; 6 | 7 | inviterEmail: string; 8 | 9 | date: Date; 10 | 11 | uid: string; 12 | 13 | projectId: string; 14 | } -------------------------------------------------------------------------------- /cn/Script/Global_Func.md: -------------------------------------------------------------------------------- 1 | Global Function 可以在Project里定义,然后在这个Project下的所有Collection或Request都可以使用这里的脚本。 2 | 3 | 通常,Global Function用来定义一些通用函数之类。 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/global_func.png) 6 | 7 | 脚本API参考: [脚本API](API.md). -------------------------------------------------------------------------------- /api/src/models/stress_failed_info.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; 2 | 3 | @Entity() 4 | export class StressFailedInfo { 5 | 6 | @PrimaryGeneratedColumn() 7 | id: number; 8 | 9 | @Column('longtext') 10 | info: string; 11 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": false, 3 | "tslint.run": "onSave", 4 | "todohighlight.include": "{**/*.ts,**/*.html,**/*.php,**/*.css}", 5 | "auto-close-tag.SublimeText3Mode": true, 6 | "typescript.tsdk": "client\\node_modules\\typescript\\lib" 7 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_stress_record.ts: -------------------------------------------------------------------------------- 1 | import { StressRunResult } from './dto_stress_setting'; 2 | 3 | export interface DtoStressRecord { 4 | 5 | id: number; 6 | 7 | stressId: string; 8 | 9 | result: StressRunResult; 10 | 11 | createDate: Date; 12 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_header.ts: -------------------------------------------------------------------------------- 1 | export interface DtoHeader { 2 | 3 | id?: string; 4 | 5 | key?: string; 6 | 7 | value?: string; 8 | 9 | isActive: boolean; 10 | 11 | isFav?: boolean; 12 | 13 | description?: string; 14 | 15 | sort?: number; 16 | } -------------------------------------------------------------------------------- /client/src/state/request.ts: -------------------------------------------------------------------------------- 1 | import { RequestStatus } from '../misc/request_status'; 2 | 3 | export interface RequestState { 4 | 5 | status: RequestStatus; 6 | 7 | message?: string; 8 | } 9 | 10 | export const requestStateDefaultValue = { status: RequestStatus.none, message: '' }; -------------------------------------------------------------------------------- /cn/Script/Pre_Script.md: -------------------------------------------------------------------------------- 1 | Pre Request Script是定义在Request里,会在Request请求发送前运行,可以在这里做些数据的准备以及修改Request请求的内容。 2 | 3 | 例如,你可以在请求发送前,添加一个hash签名到请求的url或者给请求加个时间戳的header。 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_pre_script.png) 6 | 7 | 脚本API参考: [脚本API](API.md). -------------------------------------------------------------------------------- /deploy/docker/nginx/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | nginx: 4 | image: nginx:stable-alpine 5 | restart: unless-stopped 6 | network_mode: host 7 | volumes: 8 | - ./nginx.conf:/etc/nginx/nginx.conf 9 | # doc volumn 10 | ports: 11 | - "80:80" -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015" 5 | ], 6 | "plugins": [ 7 | [ 8 | "import", 9 | { 10 | "libraryName": "antd", 11 | "style": true 12 | } 13 | ] 14 | ] 15 | } -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env 15 | npm-debug.log* 16 | yarn-debug.log* 17 | yarn-error.log* 18 | 19 | -------------------------------------------------------------------------------- /client/src/state/local_data.ts: -------------------------------------------------------------------------------- 1 | import { RequestState, requestStateDefaultValue } from './request'; 2 | 3 | export class LocalDataState { 4 | 5 | fetchLocalDataState: RequestState; 6 | } 7 | 8 | export const localDataDefaultValue: LocalDataState = { 9 | fetchLocalDataState: requestStateDefaultValue 10 | }; -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_environment.ts: -------------------------------------------------------------------------------- 1 | import { DtoVariable } from './dto_variable'; 2 | import { DtoProject } from './dto_project'; 3 | 4 | export interface DtoEnvironment { 5 | 6 | id: string; 7 | 8 | name: string; 9 | 10 | project: Partial; 11 | 12 | variables: DtoVariable[]; 13 | } -------------------------------------------------------------------------------- /api/src/middlewares/route_failed.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '../utils/message'; 2 | 3 | export default function routeFailed(): (ctx: any, next: Function) => Promise { 4 | return async (ctx, next) => { 5 | ctx.body = { success: false, message: Message.get('apiNotExist') }; 6 | return await next(); 7 | }; 8 | } -------------------------------------------------------------------------------- /api/src/run_engine/process/base_process_handler.ts: -------------------------------------------------------------------------------- 1 | import * as childProcess from 'child_process'; 2 | 3 | export abstract class BaseProcessHandler { 4 | 5 | process: childProcess.ChildProcess; 6 | 7 | call: (data?: any) => void; 8 | 9 | abstract handleMessage(msg: any); 10 | 11 | abstract afterProcessCreated(); 12 | } -------------------------------------------------------------------------------- /cn/Script/custom-data-file.md: -------------------------------------------------------------------------------- 1 | 如 [脚本API](API.md) 所提,Hitchhiker支持读取上传上来的数据文件。 2 | 3 | 在Project的菜单有一项叫`Project Data`,单击它会弹出一个数据管理对话框: 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_data.png) 6 | 7 | 你可以通过点击上传按钮来上传你的测试数据,上传完成后可以在列表里看到该项。 8 | 9 | 然后就可以通过在脚本里使用`readFile`来读取这个文件了。 10 | 11 | -------------------------------------------------------------------------------- /api/src/utils/message.ts: -------------------------------------------------------------------------------- 1 | import { Setting } from './setting'; 2 | 3 | export class Message { 4 | 5 | static en = require('../locales/en.json'); 6 | 7 | static zh = require('../locales/zh.json'); 8 | 9 | static get(id: string) { 10 | return (this[Setting.instance.appLanguage] || this['en'])[id]; 11 | } 12 | } -------------------------------------------------------------------------------- /client/src/components/tab_dot/style/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | 3 | .tab-dot-parent{ 4 | position: relative; 5 | } 6 | 7 | .tab-dot{ 8 | position: absolute; 9 | border: 3px solid @primary-color; 10 | border-radius: 3px; 11 | } 12 | 13 | .tab-dot-red{ 14 | border-color: #f04134; 15 | } -------------------------------------------------------------------------------- /client/src/utils/urls.ts: -------------------------------------------------------------------------------- 1 | export class Urls { 2 | 3 | static host = 'http://localhost:3000/'; 4 | 5 | static getUrl(action: string): string { 6 | return `${Urls.host}api/${action}`; 7 | } 8 | 9 | static getWebSocket(action: string): string { 10 | return `ws${Urls.host.substr(4)}${action}`; 11 | } 12 | } -------------------------------------------------------------------------------- /cn/other.md: -------------------------------------------------------------------------------- 1 | #### 自动同步数据 2 | 3 | Hitchhiker会自动把Project下任一成员的修改同步给所有人,默认的同步时间间隔为30秒,你可以在`appconfig.json`文件中修改, 或者在部署时设置环境变量 `HITCHHIKER_SYNC_INTERVAL`。 4 | 5 | 具体结节参考:[Configuration](installation/configuration.md) 6 | 7 | #### 浏览器端的本地数据缓存 8 | 9 | 在Hitchhiker页面上所有的操作和修改都会自动保存到浏览器的缓存里,所以不用担心在你不小心刷新浏览器或关掉电脑后没保存的修改会丢失,Hitchhiker会帮你把这些都记录下,再次打开一切都会回来。 -------------------------------------------------------------------------------- /en/Script/Global_Func.md: -------------------------------------------------------------------------------- 1 | Global Function is defined in Project which you can get it in all Collection/Request in this Project. 2 | 3 | Usually, it's used to write some common function such as util. 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/global_func.png) 6 | 7 | Script API reference to: [Script API](API.md). -------------------------------------------------------------------------------- /api/src/mail/template_setting.ts: -------------------------------------------------------------------------------- 1 | export class TemplateSetting { 2 | private _setting: any; 3 | 4 | static readonly instance = new TemplateSetting(); 5 | 6 | private constructor() { 7 | this._setting = require('../../mail.json'); 8 | } 9 | 10 | get templates() { 11 | return this._setting.templates; 12 | } 13 | } -------------------------------------------------------------------------------- /client/config/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('autoprefixer')({ 4 | browsers: [ 5 | '>1%', 6 | 'last 4 versions', 7 | 'Firefox ESR', 8 | 'not ie < 9', // React doesn't support IE8 anyway 9 | ] 10 | }) 11 | ] 12 | } -------------------------------------------------------------------------------- /cn/Schedule/README.md: -------------------------------------------------------------------------------- 1 | Collection 可以做为Request的集合一起来跑,而且跑的时候可以选择不同的环境,甚至同时选两个环境并对这两个环境返回的数据进行对比,除了环境外,你也可以让Request按照一定的顺序来跑,跑的过程中会校验Request里写的Test case是否正确并在最终结果里显示出来。 2 | 3 | Hitchhiker 支持这种以Collection为单位的Schedule,你可以借此轻松实现API的自动化测试以及不同环境的数据比对。 4 | 5 | 按照下面步骤来运行一个Schedule: 6 | 7 | 1. [创建一个Schedule](Create_Schedule.md) 8 | 9 | 2. [运行Schedule](Run.md) 10 | -------------------------------------------------------------------------------- /client/src/components/code_snippet_dialog/style/index.less: -------------------------------------------------------------------------------- 1 | 2 | .codesnippet-dropdownicon{ 3 | float: right; 4 | margin-top: 4px; 5 | } 6 | 7 | .codesnippet-dropdownbtn{ 8 | width: 200px; 9 | text-align: left; 10 | } 11 | 12 | .codesnippet-toolbar{ 13 | margin-bottom: 8px; 14 | } 15 | 16 | .codesnippet-copy{ 17 | float: right; 18 | } -------------------------------------------------------------------------------- /client/src/components/font_icon/style/index.less: -------------------------------------------------------------------------------- 1 | .font-icon { 2 | font-family: SourceCodePro, Arial, "Microsoft YaHei", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Helvetica Neue", Helvetica, "Helvetica Neue For Number", sans-serif; 3 | font-size: 10px; 4 | font-weight: bold; 5 | margin: auto; 6 | margin-top: 1px; 7 | } -------------------------------------------------------------------------------- /cn/Script/custom-javascript-lib.md: -------------------------------------------------------------------------------- 1 | 如 [脚本API](API.md) 所提,Hitchhiker支持上传自定义的js脚本库。 2 | 3 | 在Project的菜单有一项叫`Project Lib`,单击它会弹出一个js库管理对话框: 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_lib.png) 6 | 7 | 你可以先去NPM找到你想要用的js库,下载下来(最好是转成单文件),打包成zip,然后通过点击上传按钮来上传这个库,上传完成后可以在列表里看到该项。 8 | 9 | 然后就可以通过在脚本里使用`require`来引用及使用这个库了。 10 | -------------------------------------------------------------------------------- /deploy/docker/push_docker.sh: -------------------------------------------------------------------------------- 1 | export HITCHHIKER_VERSION="v0.14" 2 | sudo docker pull registry.us-east-1.aliyuncs.com/brook/hitchhiker:$HITCHHIKER_VERSION 3 | sudo docker login --username=brookshi --password=$DOCKER_ID_PWD 4 | sudo docker tag registry.us-east-1.aliyuncs.com/brook/hitchhiker:$HITCHHIKER_VERSION brookshi/hitchhiker:$HITCHHIKER_VERSION 5 | sudo docker push brookshi/hitchhiker -------------------------------------------------------------------------------- /client/src/common/enum/period.ts: -------------------------------------------------------------------------------- 1 | export enum TimerType { 2 | 3 | Minute = 1, 4 | 5 | Hour = 2, 6 | 7 | Day = 3 8 | } 9 | 10 | export enum Period { 11 | 12 | daily = 1, 13 | 14 | monday = 2, 15 | 16 | tuesday = 3, 17 | 18 | wednesday = 4, 19 | 20 | thursday = 5, 21 | 22 | friday = 6, 23 | 24 | saturday = 7, 25 | 26 | sunday = 8 27 | } -------------------------------------------------------------------------------- /api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "emitDecoratorMetadata": true, 5 | "module": "commonjs", 6 | "target": "ES6", 7 | "outDir": "build", 8 | "rootDir": "src", 9 | "sourceMap": true, 10 | "watch": true 11 | }, 12 | "exclude": [ 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /deploy/docker/private/update.sh: -------------------------------------------------------------------------------- 1 | curl -O https://raw.githubusercontent.com/brookshi/Hitchhiker/release/deploy/docker/hitchhiker/docker-compose.yml 2 | sed -i "s#localhost:8080#www.hitchhiker-api.com#g" docker-compose.yml 3 | sed -i "s/\#//g" docker-compose.yml 4 | rm -r /usr/src/Hitchhiker/build/public/ 5 | cp -rf /usr/src/Hitchhiker/client/build/. /usr/src/Hitchhiker/build/public/ 6 | pm2 restart index -------------------------------------------------------------------------------- /client/config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | // This is a custom Jest transformer turning file imports into filenames. 6 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 7 | 8 | module.exports = { 9 | process(src, filename) { 10 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /cn/Script/Common_Pre_Script.md: -------------------------------------------------------------------------------- 1 | Common Pre Request Script 是Collection级别的,在Collection的菜单里可以找到,可以用来对Collection下的Request做一些通用的事情。 2 | 3 | 一个典型的应用场景是Collection下面所有的Request的url都需要在发送前加一个动态hash值,如果每个Request都去写的话就显得很麻烦,而且维护起来也不方便,所以最好的办法就是把这些通用的事情放到Collection 级别来做。 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_common_pre_script.png) 6 | 7 | 脚本API参考: [脚本API](API.md). -------------------------------------------------------------------------------- /api/src/run_engine/process/schedule_process_handler.ts: -------------------------------------------------------------------------------- 1 | import { BaseProcessHandler } from './base_process_handler'; 2 | 3 | export class ScheduleProcessHandler extends BaseProcessHandler { 4 | 5 | handleMessage() { } 6 | 7 | afterProcessCreated() { 8 | this.process.send('start'); 9 | } 10 | 11 | reloadLib() { 12 | this.process.send('reload_project_data'); 13 | } 14 | } -------------------------------------------------------------------------------- /client/config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /client/src/utils/global_var.ts: -------------------------------------------------------------------------------- 1 | export class GlobalVar { 2 | 3 | static instance: GlobalVar = new GlobalVar(); 4 | 5 | private constructor() { } 6 | 7 | lastSyncDate: Date = new Date(); 8 | 9 | isUserInfoSyncing: boolean = false; 10 | 11 | enableUploadProjectData: boolean = true; 12 | 13 | schedulePageSize: number = 50; 14 | 15 | lang = 'hitchhiker_en'.replace('hitchhiker_', ''); 16 | } -------------------------------------------------------------------------------- /client/src/components/sider_layout/styles/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | 3 | .main-panel { 4 | background: @panel-background; 5 | } 6 | 7 | .main-sider { 8 | border-right: @border-color-dark 1px solid; 9 | box-shadow: 1px 2px 5px 0px @border-color-dark; 10 | z-index: 20; 11 | background: @panel-background; 12 | .ant-layout-sider-children { 13 | height: 100%; 14 | } 15 | } -------------------------------------------------------------------------------- /client/src/components/validated_name/style/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | 3 | .validated-name-form { 4 | .ant-form-item{ 5 | margin-bottom: 0px; 6 | } 7 | } 8 | 9 | .validated-name { 10 | input { 11 | background: @panel-background; 12 | border-width: 0px 0px 1px 0px; 13 | font-weight: 600; 14 | font-size: 15px; 15 | border-radius: 0px; 16 | } 17 | } -------------------------------------------------------------------------------- /en/Script/Pre_Script.md: -------------------------------------------------------------------------------- 1 | Pre Request Script is defined in Request which run before request is sent. You can prepare some data and modify request in here. 2 | 3 | For example, you can add a hash sign to request's url or add a timestamp in request's headers before request is sent. 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_pre_script.png) 6 | 7 | Script API reference to: [Script API](API.md). -------------------------------------------------------------------------------- /api/src/middlewares/async_init.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionManager } from '../services/connection_manager'; 2 | 3 | export default function asyncInit(): (ctx: any, next: Function) => Promise { 4 | let isAsyncInit = false; 5 | return async (_ctx, next) => { 6 | if (!isAsyncInit) { 7 | isAsyncInit = true; 8 | await ConnectionManager.init(); 9 | } 10 | await next(); 11 | }; 12 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_variable.ts: -------------------------------------------------------------------------------- 1 | export interface DtoVariable { 2 | 3 | id: string; 4 | 5 | key?: string; 6 | 7 | value?: string; 8 | 9 | isActive: boolean; 10 | 11 | sort: number; 12 | } 13 | 14 | export interface DtoQueryString extends DtoVariable { 15 | 16 | description?: string; 17 | } 18 | 19 | export interface DtoBodyFormData extends DtoVariable { 20 | 21 | description?: string; 22 | } -------------------------------------------------------------------------------- /en/other.md: -------------------------------------------------------------------------------- 1 | #### Sync data automatically 2 | 3 | Auto sync Project's data to all team members. 4 | Default interval is 30s,you can change in appconfig.json (syncInterval),or set env variable while installing (HITCHHIKER_SYNC_INTERVAL). 5 | 6 | #### Browser data save in local automatically 7 | 8 | Every thing you changed in browser will be auto saved in browser's storage, so if you close browser or compute by accident, don't worry, Hitchhiker will keep you changes. -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [{ 6 | "taskName": "build", 7 | "command": "tsc", 8 | "isShellCommand": true, 9 | "dependsOn": ["gulp"], 10 | "args": [ 11 | "-p", 12 | "api" 13 | ], 14 | "problemMatcher": "$tsc-watch" 15 | }] 16 | } -------------------------------------------------------------------------------- /api/src/models/localhost_mapping.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import { Project } from './project'; 3 | 4 | @Entity() 5 | export class LocalhostMapping { 6 | @PrimaryColumn() 7 | id: string; 8 | 9 | @Column() 10 | userId: string; 11 | 12 | @Column({ default: 'localhost' }) 13 | ip: string; 14 | 15 | @ManyToOne(_type => Project, project => project.localhosts) 16 | project: Project; 17 | } -------------------------------------------------------------------------------- /api/src/middlewares/error_handle.ts: -------------------------------------------------------------------------------- 1 | import { Log } from '../utils/log'; 2 | 3 | export default function errorHandle(): (ctx: any, next: Function) => Promise { 4 | return async (ctx, next) => { 5 | try { 6 | await next(); 7 | } catch (err) { 8 | Log.error(err); 9 | ctx.status = err.status || 500; 10 | ctx.body = err.message; 11 | ctx.app.emit('error', err, ctx); 12 | } 13 | }; 14 | } -------------------------------------------------------------------------------- /client/src/components/confirm_dialog/index.ts: -------------------------------------------------------------------------------- 1 | import { Modal } from 'antd'; 2 | import LocalesString from '../../locales/string'; 3 | 4 | export function confirmDlg(title: string | React.ReactNode, onOk: (func: Function) => any, action: string | React.ReactNode = 'delete') { 5 | Modal.confirm({ 6 | title, 7 | content: LocalesString.get('Common.ConfirmContent', { action }), 8 | okText: 'Yes', 9 | cancelText: 'No', 10 | onOk, 11 | }); 12 | } -------------------------------------------------------------------------------- /client/src/misc/http_method.ts: -------------------------------------------------------------------------------- 1 | 2 | export type HttpMethodType = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE' | string; 3 | 4 | export class HttpMethod { 5 | static GET = 'GET'; 6 | static POST = 'POST'; 7 | static PUT = 'PUT'; 8 | static DELETE = 'DELETE'; 9 | static PATCH = 'PATCH'; 10 | static HEAD = 'HEAD'; 11 | static OPTIONS = 'OPTIONS'; 12 | static CONNECT = 'CONNECT'; 13 | static TRACE = 'TRACE'; 14 | } -------------------------------------------------------------------------------- /client/src/style/colors.less: -------------------------------------------------------------------------------- 1 | @panel-background: #fafafa; 2 | 3 | @panel-background-dark: #f1f1f1; 4 | 5 | @panel-background-deep-dark: #333; 6 | 7 | @primary-color: #108ee9; 8 | 9 | @primary-color-dark: #006cb9; 10 | 11 | @primary-color-light: #80bfec; 12 | 13 | @primary-color-lighter: #bfe5ff; 14 | 15 | @border-color: #d9d9d9; 16 | 17 | @border-color-dark: #cccccc; 18 | 19 | @border-color-darker: #999999; 20 | 21 | @place-holder-color: #9a9a9a; 22 | 23 | @warning-color: #ff2222; -------------------------------------------------------------------------------- /api/logconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "appenders": [{ 3 | "type": "file", 4 | "filename": "logs/log_file.log", 5 | "maxLogSize": 20480000, 6 | "backups": 10, 7 | "category": "default" 8 | }, 9 | { 10 | "type": "console", 11 | "category": "console" 12 | } 13 | ], 14 | "levels": { 15 | "relative-logger": "ALL", 16 | "console": "ALL" 17 | }, 18 | "replaceConsole": true 19 | } -------------------------------------------------------------------------------- /client/src/action/document.ts: -------------------------------------------------------------------------------- 1 | 2 | export const DocumentActiveRecordType = 'active document record'; 3 | 4 | export const DocumentCollectionOpenKeysType = 'document collection open keys'; 5 | 6 | export const DocumentSelectedProjectChangedType = 'document selected project changed'; 7 | 8 | export const ScrollDocumentType = 'scroll document'; 9 | 10 | export const DocumentActiveCollectionType = 'document active collection'; 11 | 12 | export const DocumentActiveEnvIdType = 'document active env id'; -------------------------------------------------------------------------------- /client/src/components/record_timeline/style/index.less: -------------------------------------------------------------------------------- 1 | .record-timeline-item{ 2 | pre { 3 | white-space: pre-wrap; 4 | } 5 | 6 | .record-timeline-item-detail{ 7 | margin-top: 4px; 8 | } 9 | 10 | .record-timeline-item-detailitem { 11 | border: 1px #d1d1d1 solid; 12 | border-radius: 4px; 13 | padding: 4px; 14 | margin: 4px 0px; 15 | } 16 | 17 | .record-timeline-item-detailitem-inline { 18 | margin-left: 8px; 19 | } 20 | } -------------------------------------------------------------------------------- /en/Schedule/README.md: -------------------------------------------------------------------------------- 1 | Collection can be run together as a series of requests with different Environment. You can write test cases in Test Script, pass data between requests by using `setEnvVariable` or `File`, and sort requests as your actual use case of APIs. 2 | 3 | Hitchhiker support to add a Schedule to your Collection to do automate API testing and response data comparison. 4 | 5 | Follow these step to run a schedule: 6 | 7 | 1. [Create a Schedule](Create_Schedule.md) 8 | 9 | 2. [Run Schedule](Run.md) 10 | -------------------------------------------------------------------------------- /en/Script/Common_Pre_Script.md: -------------------------------------------------------------------------------- 1 | Common Pre Request Script is defined in Collection, used to apply a common action for Requests in a Collection. 2 | 3 | A typical scenario is Requests in a Collection need a hash sign in url, we can write this common action in Common Pre Request Script of Collection rather than write it in Pre Request Script of each request. 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_common_pre_script.png) 6 | 7 | Script API reference to: [Script API](API.md). -------------------------------------------------------------------------------- /client/debugwebpack.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./config/webpack.config.dev'); 4 | 5 | 6 | new WebpackDevServer(webpack(config), { 7 | publicPath: config.output.publicPath, 8 | hot: true, 9 | noInfo: false, 10 | historyApiFallback: true 11 | }).listen(3000, '127.0.0.1', function (err, result) { 12 | if (err) { 13 | console.log(err); 14 | } 15 | console.log('Listening at localhost:3000'); 16 | }); -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_project_data.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash'; 2 | 3 | export interface ProjectData { 4 | 5 | name: string; 6 | 7 | path: string; 8 | 9 | size: number; 10 | 11 | createdDate: Date; 12 | } 13 | 14 | export interface ProjectFiles { 15 | 16 | globalJS: _.Dictionary; 17 | 18 | projectJS: _.Dictionary<_.Dictionary>; 19 | 20 | globalData: _.Dictionary; 21 | 22 | projectData: _.Dictionary<_.Dictionary>; 23 | } -------------------------------------------------------------------------------- /cn/Variable/README.md: -------------------------------------------------------------------------------- 1 | 变量在以下这些情况下会非常有用: 2 | 3 | 1. 请求处在多环境状态下。 4 | 5 | 2. 请求有很多动态的参数在url, header或body中。 6 | 7 | 3. 请求之前互相有依赖。 8 | 9 | 4. 需要在请求发送前改变请求本身。 10 | 11 | 变量总是一个键值对,在使用时的格式为: `{{key}}`,你可在以这些地方来使用变量:`Common Pre Request Script`, `Pre Request Script`, `Test`, `Url`, `Body` 以及 `Header`。 12 | 13 | 所有的 `{{key}}` 在请求发送前或后都会被替换成变量对应的值,请求及变量运行流程可以参考图:[请求流程图](../Script/README.md). 14 | 15 | Hitchhiker 有3种类型的变量: 16 | 17 | 1. [环境变量](Env_Var.md) 18 | 19 | 2. [Parameter变量](Param_Var.md) 20 | 21 | 3. [运行时变量](Dynamic_Var.md) -------------------------------------------------------------------------------- /cn/installation/README.md: -------------------------------------------------------------------------------- 1 | Hitchhiker 是一个基于Nodejs的跨平台Web程序,你可以部署到 Linux, Mac or Windows。 2 | 另外,Hitchhiker 在Docker Hub上也有镜像可以使用,所以推荐的方式还是用Docker来部署,不论是首次还是以后升级都会更容易。 3 | 4 | Hitchhiker 有很多参数可以在部署时设置,参考:[配置文件](configuration.md)。 5 | 默认是英文版,中文版需要加入环境变量HITCHHIKER_APP_LANG=zh, 具体怎样加环境变量,请参考各自的安装文档. 6 | 7 | 1. [Docker](docker.md) (强烈推荐的方式,简单且升级方便) 8 | 9 | 2. [安装包](StepByStep.md) 10 | 11 | Hitchhiker 会在邀请Project成员或跑Schedule时发送邮件,用的是一个外部的邮箱系统,但是用户的服务器经常不能访问外网,所以Hitchhiker 提供了自定义Mail的方式,支持SMTP和API方式,具体参考:[自定义邮箱](Mail_Interface.md)。 -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_schedule_record.ts: -------------------------------------------------------------------------------- 1 | import { RunResult } from './dto_run_result'; 2 | import * as _ from 'lodash'; 3 | 4 | export interface DtoScheduleRecord { 5 | 6 | id: number; 7 | 8 | scheduleId: string; 9 | 10 | duration: number; 11 | 12 | result: { origin: Array>, compare: Array> }; 13 | 14 | success: boolean; 15 | 16 | isScheduleRun: boolean; 17 | 18 | runDate: Date; 19 | 20 | createDate: Date; 21 | } -------------------------------------------------------------------------------- /client/src/state/environment.ts: -------------------------------------------------------------------------------- 1 | import { DtoEnvironment } from '../common/interfaces/dto_environment'; 2 | import * as _ from 'lodash'; 3 | 4 | export interface EnvironmentState { 5 | 6 | environments: _.Dictionary; 7 | 8 | activeEnv: _.Dictionary; 9 | 10 | isEditEnvDlgOpen: boolean; 11 | 12 | editedEnvironment?: string; 13 | } 14 | 15 | export const environmentDefaultValue: EnvironmentState = { 16 | environments: {}, 17 | activeEnv: {}, 18 | isEditEnvDlgOpen: false 19 | }; -------------------------------------------------------------------------------- /cn/Stress/Node.md: -------------------------------------------------------------------------------- 1 | [Hitchhiker-Node](https://github.com/brookshi/Hitchhiker-Node) 是一个独立的,基于Golang的,跨平台的应用程序。 2 | 3 | 你可以在[这里](https://github.com/brookshi/Hitchhiker-Node/releases) 下载到你想要平台的运行文件。下载完成解压后会有两个文件: Hitchhiker-Node是一个可执行文件,也是主体文件,另一个config.json 是一个配置文件,打开这个配置文件,把Address的值改了你部署Hitchhiker的ip,如(192.168.0.2:11010)。 4 | 5 | 现在你就可以在这台电脑上运行这个程序,它会连Hitchhiker server,当然,你也可以把这个文件放到多台电脑上去执行,这个在做压力测试时,系统会根据这些压力点的CPU核数来分配相应的任务,这样可以支持更大的压力。 6 | 7 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stress/stress_node.png) -------------------------------------------------------------------------------- /cn/Simple_Tutorial/Assert_Base_On_UI.md: -------------------------------------------------------------------------------- 1 | #### 基于UI的断言 2 | 3 | 断言可以帮忙测试更好的测出返回数据的正确性问题,一般来说通过脚本来创建断言会更灵活,但是对于QA来说,对脚本可能不太熟悉,或者说熟悉其他脚本但不熟悉js,写起来就不太容易了。 4 | 5 | 针对这种情况推出了基于UI的断言,只需要用鼠标点点,写写目标值就可以了。 6 | 7 | 里面也可以有一些稍复杂些的函数,比如custom,这个可以根据当前校验的对象来使用不同的函数,使用的是js语法,比如string类型的,可以使用contains, match之类,如果是数组,则可以使用find(v=>v==='a') != null之类。 8 | 9 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/assert.PNG) 10 | 11 | #### 如何创建 12 | 13 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/assert.gif) -------------------------------------------------------------------------------- /en/Simple_Tutorial/Assert_Base_On_UI.md: -------------------------------------------------------------------------------- 1 | #### Assert base on UI 2 | 3 | QA could use assertions to validate if response is right, of course, use script is a good way to do this, but for some QA, it's not easy to write js script. 4 | For this, hitchhiker support set assertions base on UI, only need click and enter target value. 5 | 6 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/assert.PNG) 7 | 8 | #### How to create 9 | 10 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/assert.gif) -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_mock_collection.ts: -------------------------------------------------------------------------------- 1 | import { DtoHeader } from './dto_header'; 2 | import * as _ from 'lodash'; 3 | import { DtoMock } from './dto_mock'; 4 | 5 | export interface DtoMockCollection { 6 | 7 | id: string; 8 | 9 | name: string; 10 | 11 | projectId: string; 12 | 13 | headers: DtoHeader[]; 14 | 15 | description: string; 16 | } 17 | 18 | export interface DtoCollectionWithMock { 19 | 20 | collections: _.Dictionary; 21 | 22 | mocks: _.Dictionary<_.Dictionary>; 23 | } -------------------------------------------------------------------------------- /en/Script/custom-javascript-lib.md: -------------------------------------------------------------------------------- 1 | As mention in [Script API](API.md), Hitchhiker support custom javascript libs. 2 | 3 | In Project's menu, there is a item named Project Lib, it will pop a lib manager dialog like below: 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_lib.png) 6 | 7 | You could find js lib that you want in NPM, pack it to zip file and upload it. 8 | 9 | You can see it in the grid if file uploaded completely. 10 | 11 | Then you can use function `require` in script to use this lib. 12 | -------------------------------------------------------------------------------- /en/Script/custom-data-file.md: -------------------------------------------------------------------------------- 1 | As mention in [Script API](API.md), Hitchhiker support prepared file data for test case. 2 | 3 | In Project's menu, there is a item named Project Data, it will pop a data manager dialog like below: 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_data.png) 6 | 7 | You could upload your test case data file directly by hit Upload button. 8 | 9 | You can see it in the grid if file uploaded completely. 10 | 11 | Then you can use function `readFile` to read this file in script. 12 | 13 | -------------------------------------------------------------------------------- /cn/Stress/Create_Stress.md: -------------------------------------------------------------------------------- 1 | 进到Stress模块然后点击`create stress test` 按钮. 2 | 3 | 在弹出的对话框里填写参数: 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stress/stress_create.png) 6 | 7 | **Name**: stress test的名字. 8 | 9 | **Collection**: 需要做压力测试的Collection. 10 | 11 | **Requests**: 选择要跑的请求以及排序. 12 | 13 | **Repeat**: 重复次数. 14 | 15 | **Concurrency**: 并发数. 16 | 17 | **QPS**: 每个压力点每秒请求数,默认为0,即没有限制. 18 | 19 | **Timeout**: 请求的超时时间,单位为秒,默认为0,即没有限制. 20 | 21 | **Keeplive**: 请求是否设置Keeplive. 22 | 23 | **Environment**: 要跑的环境. 24 | 25 | 设置完成后单击OK按钮完成创建。 -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_mail.ts: -------------------------------------------------------------------------------- 1 | import { DtoError } from './dto_error'; 2 | 3 | export interface MailScheduleRecord { 4 | 5 | scheduleName: string; 6 | 7 | success: boolean; 8 | 9 | duration: number; 10 | 11 | runResults: MailRunResult[]; 12 | } 13 | 14 | export interface MailRunResult { 15 | 16 | recordName: string; 17 | 18 | parameter: string; 19 | 20 | envName: string; 21 | 22 | tests: { [key: string]: boolean }; 23 | 24 | duration: number; 25 | 26 | error?: DtoError; 27 | 28 | isSuccess: boolean; 29 | } -------------------------------------------------------------------------------- /client/src/components/res_error_panel/style/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | .res-error-header { 3 | margin-left: -10px; 4 | padding-left: 14px; 5 | line-height: 36px; 6 | font-size: 15px; 7 | color: @place-holder-color; 8 | border-top: 1px solid @border-color; 9 | border-bottom: 1px solid @border-color; 10 | } 11 | 12 | .res-error-title { 13 | font-size: 18px; 14 | margin: 12px 0px; 15 | } 16 | 17 | .res-error-desc { 18 | margin-bottom: 12px; 19 | >span { 20 | color: @primary-color; 21 | } 22 | } -------------------------------------------------------------------------------- /api/typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalDependencies": { 3 | "cookies": "registry:dt/cookies#0.5.1+20160316171810", 4 | "koa": "registry:dt/koa#2.0.0+20161025101853", 5 | "koa-bodyparser": "registry:dt/koa-bodyparser#3.0.0+20160928122441", 6 | "koa-router": "registry:dt/koa-router#7.0.0+20160907102352", 7 | "koa-session-minimal": "registry:dt/koa-session-minimal#3.0.0+20161019121247", 8 | "node": "registry:dt/node#6.0.0+20161121110008" 9 | }, 10 | "dependencies": { 11 | "koa-compose": "registry:npm/koa-compose#3.0.0+20160723033700" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cn/Simple_Tutorial/README.md: -------------------------------------------------------------------------------- 1 | 这里是一个Hitchhiker的简单使用教程。 2 | 3 | Hitchhiker 有4个模块,另外2个(Document, Mock)还在开发中): 4 | 5 | Collection, Project, Schedule, Stress Test 6 | 7 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/simple_tutorial/modules.png) 8 | 9 | 可以通过完成下面步骤来熟悉Hitchhiker: 10 | 11 | 1. [创建一个Project](Create_Project.md) 12 | 13 | 2. [创建一个Collection](Create_Collection.md) 14 | 15 | 3. [创建一个Request并发送请求](Create_Request.md) 16 | 17 | 4. [使用环境变量](Use_Env_Var.md) 18 | 19 | 5. [使用Parameters](Use_Param.md) 20 | 21 | 6. [基于UI的断言](Assert_Base_On_UI.md) -------------------------------------------------------------------------------- /api/src/models/stress_record.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn } from 'typeorm'; 2 | import { Stress } from './stress'; 3 | import { StressRunResult } from '../common/interfaces/dto_stress_setting'; 4 | 5 | @Entity() 6 | export class StressRecord { 7 | 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @ManyToOne(_type => Stress, stress => stress.stressRecords) 12 | stress: Stress; 13 | 14 | @Column('json') 15 | result: StressRunResult; 16 | 17 | @CreateDateColumn() 18 | createDate: Date; 19 | } -------------------------------------------------------------------------------- /client/src/misc/schedule_statistics.ts: -------------------------------------------------------------------------------- 1 | import { RunResult } from '../common/interfaces/dto_run_result'; 2 | 3 | export interface ScheduleStatistics { 4 | 5 | id: string; 6 | 7 | key: string; 8 | 9 | runResults: Array; 10 | 11 | name: string; 12 | 13 | env: string; 14 | 15 | param?: string; 16 | 17 | successNum: number; 18 | 19 | errorNum: number; 20 | 21 | total: number; 22 | 23 | minTime: number; 24 | 25 | maxTime: number; 26 | 27 | averageTime: number; 28 | 29 | lastStatus: boolean; 30 | } -------------------------------------------------------------------------------- /cn/Variable/Dynamic_Var.md: -------------------------------------------------------------------------------- 1 | 运行时变量是在脚本里动态定义出来的,通常用于为url生成hash签名或者保存一个请求的结果,然后在另一个请求中使用。 2 | 3 | 运行时变量可以在这些脚本中定义:`Global Function`, `Common Pre Request Script`, `Pre Request Script` 以及 `Test`. 4 | 5 | 使用如下API: 6 | 7 | ```js 8 | hitchhiker.setEnvVariable('rt_var', 'test'); // 定义一个运行时变量 key: `rt_var`, value: `test` 9 | const value = hitchhiker.getEnvVariable('rt_var'); // 获取变量的 value (`test`) 10 | ``` 11 | 12 | 一个典型的场景: 13 | 14 | 有4个APIs: `create`, `select`, `update`, `delete`. 15 | `create` API用于创建一项并返回id。 16 | 其他3个API依赖于这个id来做测试,这时可以把id写到运行时变量中,然后在其他3个API中获取这个id即可。 17 | 18 | -------------------------------------------------------------------------------- /api/src/models/record_doc_history.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne } from 'typeorm'; 2 | import { RecordDoc } from './record_doc'; 3 | import { User } from './user'; 4 | 5 | @Entity() 6 | export class RecordDocHistory { 7 | 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @ManyToOne(_type => RecordDoc, doc => doc.history) 12 | target: RecordDoc; 13 | 14 | @Column('json') 15 | doc: RecordDoc; 16 | 17 | @ManyToOne(_type => User) 18 | user: User; 19 | 20 | @CreateDateColumn() 21 | createDate: Date; 22 | } -------------------------------------------------------------------------------- /client/src/components/name_with_tag/index.tsx: -------------------------------------------------------------------------------- 1 | import './style/index.less'; 2 | import React from 'react'; 3 | 4 | export const nameWithTag = (name: string | React.ReactNode, tag: string, type: 'normal' | 'warning' = 'normal') => ( 5 |
6 | {name} 7 | { 8 | (tag !== '' && tag !== '0') ? 9 | ( 10 | 11 | {`(${tag})`} 12 | 13 | ) : '' 14 | } 15 |
16 | ); -------------------------------------------------------------------------------- /api/src/models/variable.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; 2 | import { Environment } from './environment'; 3 | 4 | @Entity() 5 | export class Variable { 6 | @PrimaryColumn() 7 | id: string; 8 | 9 | @Column({ nullable: true }) 10 | key: string; 11 | 12 | @Column('text', { nullable: true }) 13 | value: string; 14 | 15 | @Column({ default: false }) 16 | isActive: boolean; 17 | 18 | @Column('int') 19 | sort: number; 20 | 21 | @ManyToOne(_type => Environment, env => env.variables) 22 | environment: Environment; 23 | 24 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_res.ts: -------------------------------------------------------------------------------- 1 | import { DtoProject } from './dto_project'; 2 | 3 | export interface DtoResUser { 4 | 5 | id: string; 6 | 7 | name: string; 8 | 9 | password: string; 10 | 11 | email: string; 12 | 13 | projects: DtoProject[]; 14 | 15 | isActive: boolean; 16 | 17 | isTemp: boolean; 18 | 19 | createDate: Date; 20 | 21 | updateDate: Date; 22 | } 23 | 24 | export interface ConsoleMsg { 25 | 26 | time: Date; 27 | 28 | type: 'log' | 'info' | 'warn' | 'error' | any; 29 | 30 | message: string; 31 | 32 | custom: boolean; 33 | } -------------------------------------------------------------------------------- /client/src/locales/lang.ts: -------------------------------------------------------------------------------- 1 | import appLocaleDataEn from 'react-intl/locale-data/en'; 2 | import appLocaleDataZh from 'react-intl/locale-data/zh'; 3 | const enMessages = require('./en'); 4 | const zhMessages = require('./zh'); 5 | 6 | export const language = { 7 | en: { 8 | messages: { 9 | ...enMessages, 10 | }, 11 | locale: 'en-US', 12 | data: appLocaleDataEn 13 | }, 14 | zh: { 15 | messages: { 16 | ...zhMessages, 17 | }, 18 | antd: {}, 19 | locale: 'zh-Hans-CN', 20 | data: appLocaleDataZh 21 | } 22 | }; -------------------------------------------------------------------------------- /client/src/misc/stress_type.ts: -------------------------------------------------------------------------------- 1 | 2 | export enum WorkerStatus { 3 | 4 | idle = 0, 5 | 6 | ready = 1, 7 | 8 | working = 2, 9 | 10 | finish = 3, 11 | 12 | down = 4, 13 | 14 | fileReady = 5, 15 | } 16 | 17 | export enum StressMessageType { 18 | 19 | hardware = 0, 20 | 21 | task = 1, 22 | 23 | start = 2, 24 | 25 | runResult = 3, 26 | 27 | stop = 4, 28 | 29 | status = 5, 30 | 31 | fileStart = 6, 32 | 33 | fileFinish = 7, 34 | 35 | init, 36 | 37 | close, 38 | 39 | wait, 40 | 41 | error, 42 | 43 | finish, 44 | 45 | noWorker 46 | } -------------------------------------------------------------------------------- /client/src/components/environment_select/style/index.less: -------------------------------------------------------------------------------- 1 | 2 | @import '../../../style/colors.less'; 3 | 4 | .req-tab-extra-env{ 5 | padding: 0px 8px; 6 | border-left: 1px solid @border-color; 7 | background: @panel-background; 8 | display: inline-block; 9 | } 10 | 11 | .req-tab-extra-env-select{ 12 | width: 180px; 13 | margin-right: 4px; 14 | } 15 | 16 | .record-add-btn { 17 | border-radius: 0px; 18 | position: relative; 19 | right: 2px; 20 | top: 2px; 21 | height: 26px; 22 | border: 0px; 23 | font-size: 18px; 24 | background: #fafafa; 25 | color: #108ee9; 26 | } -------------------------------------------------------------------------------- /client/src/state/project.ts: -------------------------------------------------------------------------------- 1 | import { DtoProject } from '../common/interfaces/dto_project'; 2 | import { ProjectFiles } from '../common/interfaces/dto_project_data'; 3 | import * as _ from 'lodash'; 4 | 5 | export interface ProjectState { 6 | 7 | projects: _.Dictionary; 8 | 9 | activeProject: string; 10 | 11 | projectFiles: ProjectFiles; 12 | } 13 | 14 | export const projectDefaultValue: ProjectState = { 15 | projects: {}, 16 | activeProject: '', 17 | projectFiles: { 18 | globalJS: {}, 19 | globalData: {}, 20 | projectJS: {}, 21 | projectData: {} 22 | } 23 | }; -------------------------------------------------------------------------------- /client/src/components/editor/style/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | .req-editor { 3 | border: 1px solid @border-color; 4 | } 5 | 6 | .ace_scrollbar::-webkit-scrollbar-track { 7 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); 8 | border-radius: 8px; 9 | background-color: #F5F5F5; 10 | } 11 | 12 | .ace_scrollbar::-webkit-scrollbar { 13 | width: 8px; 14 | background-color: #F5F5F5; 15 | } 16 | 17 | .ace_scrollbar::-webkit-scrollbar-thumb { 18 | border-radius: 8px; 19 | box-shadow: inset 0 0 6px rgba(0, 0, 0, .1); 20 | background-color: #ccc; 21 | } -------------------------------------------------------------------------------- /cn/Variable/Env_Var.md: -------------------------------------------------------------------------------- 1 | 环境变量是在Project的Environment中定义的,通常环境变量用于维护不同环境下的host,header等。 2 | 3 | 举个例子: 4 | 我们的API有3个环境,分别是: QA, Stage, Product。每个环境都有自己的域名: 5 | 6 | ``` 7 | QA: http://api-qa.sample.com/ 8 | Stage: http://api-stg.sample.com/ 9 | Product: http://api.sample.com/ 10 | ``` 11 | 12 | 没有环境变量的话,我们需要为这些环境分别创建Request,这太麻烦了。 13 | 14 | 我们可以先创建出这些环境,比如QA环境,添加一个变量 key: `host`, value: `http://api-qa.sample.com/`。 15 | 复制后同样的方式做出Stage和Product环境。 16 | 17 | 现有,我们有3个环境了,可以在同一个请求里来使用它们。 18 | 创建一个请求,url: `{{host}}/get`,分别选择不同的环境来测试。 19 | 20 | `{{host}}` 会在请求发送前替换成不同环境对应的值,比如选择QA, 请求的url会是`http://api-qa.sample.com/`, 选 Stage的话就是 `http://api-stg.sample.com/`。 -------------------------------------------------------------------------------- /deploy/docker/hitchhiker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | hitchhiker: 4 | image: brookshi/hitchhiker:v0.14 5 | container_name: hitchhiker 6 | environment: 7 | - HITCHHIKER_DB_HOST=hitchhiker-mysql 8 | - HITCHHIKER_APP_HOST=http://localhost:8080/ 9 | ports: 10 | - "8080:8080" 11 | - "11010:11010" 12 | external_links: 13 | - hitchhiker-mysql:hitchhiker-mysql 14 | volumes: 15 | - /my/hitchhiker/data:/usr/src/Hitchhiker/build/global_data/project 16 | - /my/hitchhiker/backup:/usr/src/Hitchhiker/build/backup 17 | - /my/hitchhiker/logs:/usr/src/Hitchhiker/build/logs -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request (功能要求) 3 | about: Suggest an idea for this project. 4 | 5 | --- 6 | 7 | **1. Is your feature request related to a problem? Please describe. `(这个功能是为解决什么问题)`** 8 | A clear and concise description of what the problem is. Explain your use case, context, and rationale behind this feature request (请尽可能详尽地说明这个需求的用例和场景) 9 | 10 | **2. Describe the solution you'd like`(请描述你期望的结果)`** 11 | A clear and concise description of what you want to happen (简单清楚的描述你期望的结果) 12 | 13 | **3. Additional context `(补充信息)`** 14 | Add any other context or screenshots about the feature request here (截图,链接等) 15 | -------------------------------------------------------------------------------- /client/src/components/assert_json_view/assert_funcs.ts: -------------------------------------------------------------------------------- 1 | export type AssertType = 'array' | 'string' | 'number' | 'boolean'; 2 | 3 | export const arrayFuncs = ['length >', 'length ==', 'length <', 'includes', 'every', 'some', 'custom']; 4 | 5 | export const stringFuncs = ['length >', 'length ==', 'length <', '==', 'includes', 'startsWith', 'endsWith', 'custom']; 6 | 7 | export const numberFuncs = ['>', '>=', '==', '<', '<=']; 8 | 9 | export const booleanFuncs = ['true', 'false']; 10 | 11 | export const AssertTypeFuncMapping = { 12 | array: arrayFuncs, 13 | string: stringFuncs, 14 | number: numberFuncs, 15 | boolean: booleanFuncs 16 | }; -------------------------------------------------------------------------------- /en/Stress/Node.md: -------------------------------------------------------------------------------- 1 | [Hitchhiker-Node](https://github.com/brookshi/Hitchhiker-Node) is a independent, cross platform application written in Golang. 2 | 3 | You can download file from [here](https://github.com/brookshi/Hitchhiker-Node/releases) according to your system and then unzip it. There are two files: Hitchhiker-Node is a executable file and config.json is a config file, open config.json file and change value of Address to the Hitchhiker server's ip, eg: (192.168.0.2:11010). 4 | 5 | Now you get a Stress Node, you can run it or copy it to other computes and run. 6 | 7 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stress/stress_node.png) -------------------------------------------------------------------------------- /en/installation/README.md: -------------------------------------------------------------------------------- 1 | Hitchhiker is a cross platform web application base on Nodejs, you can deploy it in Linux, Mac or Windows. 2 | Hitchhiker also has a docker image in docker hub, so the recommend way is deploy it with docker, much easier. 3 | 4 | There are several setting that you can use while deploying, refer to [Configuration](configuration.md). 5 | 6 | 1. [Docker](docker.md) 7 | 8 | 2. [Package](StepByStep.md) 9 | 10 | Hitchhiker use a mail service that used to send mail for Project members invitation or Schedule run result. Sometimes user server can't access internet, in this case, you can refer to [Custom Mail Interface](Mail_Interface.md). -------------------------------------------------------------------------------- /client/scripts/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | process.env.NODE_ENV = 'test'; 4 | process.env.PUBLIC_URL = ''; 5 | 6 | // Load environment variables from .env file. Suppress warnings using silent 7 | // if this file is missing. dotenv will never modify any environment variables 8 | // that have already been set. 9 | // https://github.com/motdotla/dotenv 10 | require('dotenv').config({silent: true}); 11 | 12 | const jest = require('jest'); 13 | const argv = process.argv.slice(2); 14 | 15 | // Watch unless on CI or in coverage mode 16 | if (!process.env.CI && argv.indexOf('--coverage') < 0) { 17 | argv.push('--watch'); 18 | } 19 | 20 | 21 | jest.run(argv); 22 | -------------------------------------------------------------------------------- /client/src/components/editable_cell/style/index.less: -------------------------------------------------------------------------------- 1 | .editable-cell { 2 | position: relative; 3 | } 4 | 5 | .editable-cell-input-wrapper { 6 | padding-right: 24px; 7 | } 8 | 9 | .editable-cell-text-wrapper { 10 | padding: 5px 24px 5px 5px; 11 | } 12 | 13 | .editable-cell-icon, 14 | .editable-cell-icon-check { 15 | position: absolute; 16 | right: 0; 17 | width: 20px; 18 | cursor: pointer; 19 | } 20 | 21 | .editable-cell-icon-check { 22 | line-height: 28px; 23 | } 24 | 25 | .editable-cell:hover .editable-cell-icon { 26 | display: inline-block; 27 | } 28 | 29 | .editable-cell-icon:hover, 30 | .editable-cell-icon-check:hover { 31 | color: #108ee9; 32 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_project.ts: -------------------------------------------------------------------------------- 1 | import { DtoUser } from './dto_user'; 2 | import { DtoEnvironment } from './dto_environment'; 3 | 4 | export interface LocalhostMapping { 5 | 6 | id: string; 7 | 8 | userId: string; 9 | 10 | ip: string; 11 | } 12 | 13 | export interface DtoProject { 14 | 15 | id: string; 16 | 17 | name: string; 18 | 19 | note?: string; 20 | 21 | members?: DtoUser[]; 22 | 23 | localhosts?: Partial[]; 24 | 25 | environments?: DtoEnvironment[]; 26 | 27 | globalFunction?: string; 28 | 29 | isMe?: boolean; 30 | 31 | owner: Partial; 32 | 33 | createDate?: Date; 34 | } -------------------------------------------------------------------------------- /client/src/state/document.ts: -------------------------------------------------------------------------------- 1 | import { allProject } from '../misc/constants'; 2 | import * as _ from 'lodash'; 3 | 4 | export interface DocumentState { 5 | 6 | documentActiveRecord: string; 7 | 8 | activeEnv: _.Dictionary; 9 | 10 | documentCollectionOpenKeys: string[]; 11 | 12 | documentSelectedProject: string; 13 | 14 | scrollTop: number; 15 | 16 | changeByScroll: boolean; 17 | } 18 | 19 | export const documentDefaultValue: DocumentState = { 20 | documentActiveRecord: '', 21 | documentCollectionOpenKeys: [], 22 | activeEnv: {}, 23 | documentSelectedProject: allProject, 24 | scrollTop: 0, 25 | changeByScroll: false 26 | }; -------------------------------------------------------------------------------- /api/src/middlewares/session_handle.ts: -------------------------------------------------------------------------------- 1 | import { SessionService } from '../services/session_service'; 2 | import { Message } from '../utils/message'; 3 | 4 | export default function sessionHandle(): (ctx: any, next: Function) => Promise { 5 | return async (ctx, next) => { 6 | const isSessionValid = await SessionService.isSessionValid(ctx); 7 | if (false) { 8 | ctx.body = { success: false, message: Message.get('sessionInvalid') }; 9 | ctx.status = 403; 10 | // ctx.redirect(Setting.instance.host); 11 | return; 12 | } 13 | SessionService.rollDate(ctx); 14 | return await next(); 15 | }; 16 | } -------------------------------------------------------------------------------- /deploy/docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:7.10 2 | MAINTAINER brook.shi iwxiaot@gmail.com 3 | 4 | RUN apt-get update 5 | 6 | # code folder 7 | RUN mkdir -p /usr/src 8 | WORKDIR /usr/src 9 | RUN git clone -b release https://github.com/brookshi/Hitchhiker.git 10 | WORKDIR /usr/src/Hitchhiker 11 | 12 | # npm install -g 13 | RUN npm install -g pm2 yarn gulp-cli typescript@2.3.3 14 | RUN npm install gulp -D 15 | RUN npm install typescript@2.3.3 --save 16 | 17 | # npm install 18 | RUN npm install 19 | 20 | RUN cd client && npm install 21 | 22 | # gulp build 23 | RUN gulp release --prod 24 | 25 | WORKDIR /usr/src/Hitchhiker/build 26 | # start mail 27 | EXPOSE 8080 28 | CMD ["pm2-docker", "index.js"] 29 | -------------------------------------------------------------------------------- /en/Stress/README.md: -------------------------------------------------------------------------------- 1 | Hitchhiker has two ways to do stress test. [Hitchhiker-Node](https://github.com/brookshi/Hitchhiker-Node) Base on Go, and another base on Nodejs build-in function of Hitchhiker. 2 | 3 | Default is use function base on Nodejs and Hitchhiker-Node will be deprecated unless Golang have a js interpreter base on Nodejs. 4 | 5 | As same as Schedule, the unit of Stress test is Collection too, you can sort requests, set concurrency to emulate a real customer scenario. 6 | 7 | Follow these step to run a Stress Test: 8 | 9 | 1. [Create a Stress Test](Create_Stress.md). 10 | 11 | 2. [Run a Stress Node](Node.md). // ignore unless you want to use Hitchhiker-Node 12 | 13 | 3. [Run](Run.md). -------------------------------------------------------------------------------- /api/src/models/record_history.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, ManyToOne, JoinColumn } from 'typeorm'; 2 | import { Record } from './record'; 3 | import { User } from './user'; 4 | 5 | @Entity() 6 | export class RecordHistory { 7 | 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @ManyToOne(_type => Record, record => record.history) 12 | target: Record; 13 | 14 | @Column('json') 15 | record: Record; 16 | 17 | @ManyToOne(_type => User) 18 | @JoinColumn({ name: 'userId' }) 19 | user: User; 20 | 21 | @Column({ nullable: true }) 22 | userId: string; 23 | 24 | @CreateDateColumn() 25 | createDate: Date; 26 | } -------------------------------------------------------------------------------- /client/src/locales/input.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { injectIntl } from 'react-intl'; 3 | import { Input } from 'antd'; 4 | import { InputProps } from 'antd/lib/input/Input'; 5 | 6 | interface LoInputProps extends InputProps { 7 | 8 | intl: any; 9 | 10 | placeholderId: string; 11 | } 12 | 13 | interface LoInputState { } 14 | 15 | class LoInput extends React.Component { 16 | public render() { 17 | const { intl, placeholderId } = this.props; 18 | return ( 19 | 20 | ); 21 | } 22 | } 23 | 24 | export default injectIntl(LoInput); -------------------------------------------------------------------------------- /cn/Schedule/Create_Schedule.md: -------------------------------------------------------------------------------- 1 | 首先进入Schedule模块, 单击 `create schedule` 按钮来创建一个schedule. 2 | 3 | 这时会弹出一个创建对话框,有以下参数: 4 | 5 | **Name**: schedule的名字 6 | 7 | **Collection**: 选择你想跑的Collection 8 | 9 | **Sort request**: 按顺序来跑Collection里的请求,当这个选项勾上的话,下方会出现一个请求列表,你可以拖动它们来排序 10 | 11 | **Period**: 任务跑的时间,可以设置为每天跑,或者每小时,甚至每分钟都可以 12 | 13 | **Environment**: 选择Collection要跑的环境 14 | 15 | **Compare**: 勾上这个选项会出现一个对比环境,然后跑的时候就会分别为每个环境跑一次,最后作数据对比,当然,有些请求可能我们不想让它们进行比对,比如cookie,这类的数据比对没有意义,所以在上面的请求列表中有个开关叫`match`,关掉时就不会对比这个请求的数据 16 | 17 | **Notification**: 发送跑完的结果到邮箱里,可以自定义。 18 | 19 | 单击OK按钮完成创建。 20 | 21 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/schedule/schedule_create.png) 22 | 23 | -------------------------------------------------------------------------------- /en/Simple_Tutorial/README.md: -------------------------------------------------------------------------------- 1 | Here is a simple tutorial of using Hitchhiker. 2 | 3 | Hitchhiker has four modules(the other two (Document, Mock) are in plan): 4 | 5 | Collection, Project, Schedule, Stress Test 6 | 7 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/simple_tutorial/modules.png) 8 | 9 | Follow these steps below to get familiar with Hitchhiker: 10 | 11 | 1. [Create a Project](Create_Project.md) 12 | 13 | 2. [Create a Collection](Create_Collection.md) 14 | 15 | 3. [Create a Request and Send](Create_Request.md) 16 | 17 | 4. [Use Environment Variable](Use_Env_Var.md) 18 | 19 | 5. [Use Parameters](Use_Param.md) 20 | 21 | 6. [Assert base on UI](Assert_Base_On_UI.md) -------------------------------------------------------------------------------- /client/config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_stress.ts: -------------------------------------------------------------------------------- 1 | import { NotificationMode } from '../enum/notification_mode'; 2 | import { DtoStressRecord } from './dto_stress_record'; 3 | 4 | export interface DtoStress { 5 | 6 | id: string; 7 | 8 | name: string; 9 | 10 | collectionId: string; 11 | 12 | environmentId: string; 13 | 14 | concurrencyCount: number; 15 | 16 | repeat: number; 17 | 18 | qps: number; 19 | 20 | timeout: number; 21 | 22 | keepAlive: boolean; 23 | 24 | requests: string[]; 25 | 26 | notification: NotificationMode; 27 | 28 | emails?: string; 29 | 30 | ownerId: string; 31 | 32 | stressRecords: DtoStressRecord[]; 33 | 34 | lastRunDate?: Date; 35 | } -------------------------------------------------------------------------------- /client/src/components/indicator/style/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | 3 | @keyframes blink { 4 | from { 5 | background: @primary-color; 6 | } 7 | to { 8 | background: transparent; 9 | } 10 | } 11 | 12 | .indicator-blink { 13 | animation-name: blink; 14 | animation-duration: 1s; 15 | animation-iteration-count: infinite; 16 | animation-timing-function: ease-in; 17 | animation-direction:alternate; 18 | } 19 | 20 | .indicator-round { 21 | height: 12px; 22 | width: 12px; 23 | display: inline-block; 24 | border-radius: 6px; 25 | margin-bottom: -2px; 26 | } 27 | 28 | .indicator-name{ 29 | font-weight: bold; 30 | margin-left: 4px; 31 | } -------------------------------------------------------------------------------- /cn/Stress/Run.md: -------------------------------------------------------------------------------- 1 | 压力点准备就绪,已经运行起来的话,现在就可以尝试跑下压力测试了。 2 | 3 | 把鼠标移到Stress Test item上,在弹出的菜单里点击 `Run Now`。 4 | 5 | Hitchhiker 会显示压力测试实时状况,包括当前工作的压力点,压力测试的进度,TPS以及请求失败的状态。 6 | 7 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stress/stress_run.png) 8 | 9 | 可以看到有3个图表(从上到下): 10 | 11 | 1. 压力测试的当前进度,包括压力集中在哪些请求上,已经完成的请求数,当前的TPS。 12 | 13 | 2. 每个请求消耗的时间,包括DNS, Connect, Request, Min, Max。 14 | 15 | 3. 请示失败的状态和个数,失败有3种:No Response, Server Error(500), Test Failed. 16 | 17 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stresstest.gif) 18 | 19 | 压力请求也可以中断掉,因为可能在压力过程中服务端已经暴露出了问题,不需要再跑下去,这时可以停止当前压力测试。 20 | 21 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stress/stop.png) 22 | 23 | -------------------------------------------------------------------------------- /api/src/models/user.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryColumn, Column, UpdateDateColumn, CreateDateColumn, ManyToMany } from 'typeorm'; 2 | import { Project } from './project'; 3 | 4 | @Entity() 5 | export class User { 6 | 7 | @PrimaryColumn() 8 | id: string; 9 | 10 | @Column() 11 | name: string; 12 | 13 | @Column() 14 | password: string; 15 | 16 | @Column() 17 | email: string; 18 | 19 | @ManyToMany(_type => Project, project => project.members) 20 | projects: Project[] = []; 21 | 22 | @Column() 23 | isActive: boolean; 24 | 25 | @Column({ default: false }) 26 | isTemp: boolean; 27 | 28 | @CreateDateColumn() 29 | createDate: Date; 30 | 31 | @UpdateDateColumn() 32 | updateDate: Date; 33 | } -------------------------------------------------------------------------------- /cn/Script/Test.md: -------------------------------------------------------------------------------- 1 | 你可以在Test脚本里做一些请求是否正确返回的校验工作。 2 | 3 | Test脚本会在请求返回后开始执行,在Test里可以访问到一些关于请求返回的内置对象: 4 | 5 | ``` javascript 6 | `responseBody`: 返回的主体,字符串 7 | `responseObj`:返回的json对象 8 | `responseHeaders`: 返回的headers 9 | `responseTime`: 请求消耗的时间(毫秒) 10 | `responseCode.code`: 请求响应的状态码 11 | `responseCode.name`: 请求响应文本 12 | ``` 13 | 14 | 你可以像下面的脚本一样做数据校验: 15 | 16 | ```js 17 | tests["value is correct"] = responseObj.value === 100; 18 | 19 | tests["status code is 200"] = responseCode.code === 200; 20 | ``` 21 | 22 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/test_result.png) 23 | 24 | 还可以在脚本里把数据保存到文件,然后就可以在其他请求中使用这个文件。 25 | 26 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_test.png) -------------------------------------------------------------------------------- /client/src/components/font_icon/font_icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './style/index.less'; 3 | 4 | interface FontIconProps { 5 | text: string; 6 | 7 | color: string; 8 | 9 | size?: number; 10 | } 11 | 12 | interface FontIconState { } 13 | 14 | class FontIcon extends React.Component { 15 | public render() { 16 | return ( 17 | 24 | {this.props.text} 25 | 26 | ); 27 | } 28 | } 29 | 30 | export default FontIcon; -------------------------------------------------------------------------------- /api/src/models/query_string.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import { Record } from './record'; 3 | import { Mock } from './mock'; 4 | 5 | @Entity() 6 | export class QueryString { 7 | @PrimaryColumn() 8 | id: string; 9 | 10 | @Column({ nullable: true }) 11 | key: string; 12 | 13 | @Column('text', { nullable: true }) 14 | value: string; 15 | 16 | @Column({ default: true }) 17 | isActive: boolean; 18 | 19 | @Column() 20 | sort: number; 21 | 22 | @Column('text', { nullable: true }) 23 | description: string; 24 | 25 | @ManyToOne(_type => Record, record => record.id) 26 | record: Record; 27 | 28 | @ManyToOne(_type => Mock, mock => mock.id) 29 | mock: Mock; 30 | } -------------------------------------------------------------------------------- /client/src/components/tab_dot/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './style/index.less'; 3 | 4 | interface TabDotProps { 5 | 6 | show: boolean; 7 | 8 | important?: boolean; 9 | } 10 | 11 | interface TablWithDotProps extends TabDotProps { 12 | 13 | content: string; 14 | } 15 | 16 | export function TabWithDot(props: TablWithDotProps) { 17 | return ( 18 |
19 | {props.content} 20 | 21 |
22 | ); 23 | } 24 | 25 | export function TabDot(props: TabDotProps) { 26 | return props.show ? ( 27 | 28 | ) : null; 29 | } -------------------------------------------------------------------------------- /api/src/models/body_form_data.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import { Record } from './record'; 3 | import { Mock } from './mock'; 4 | 5 | @Entity() 6 | export class BodyFormData { 7 | @PrimaryColumn() 8 | id: string; 9 | 10 | @Column({ nullable: true }) 11 | key: string; 12 | 13 | @Column('text', { nullable: true }) 14 | value: string; 15 | 16 | @Column({ default: true }) 17 | isActive: boolean; 18 | 19 | @Column() 20 | sort: number; 21 | 22 | @Column('text', { nullable: true }) 23 | description: string; 24 | 25 | @ManyToOne(_type => Record, record => record.id) 26 | record: Record; 27 | 28 | @ManyToOne(_type => Mock, mock => mock.id) 29 | mock: Mock; 30 | } -------------------------------------------------------------------------------- /client/src/state/stress.ts: -------------------------------------------------------------------------------- 1 | import { DtoStress } from '../common/interfaces/dto_stress'; 2 | import { StressRunResult, WorkerInfo } from '../common/interfaces/dto_stress_setting'; 3 | import * as _ from 'lodash'; 4 | 5 | export interface StressTestState { 6 | 7 | stresses: _.Dictionary; 8 | 9 | activeStress: string; 10 | 11 | currentRunStressName: string; 12 | 13 | currentRunStressId: string; 14 | 15 | workerInfos: WorkerInfo[]; 16 | 17 | tasks: string[]; 18 | 19 | runState?: StressRunResult; 20 | } 21 | 22 | export const stressDefaultValue: StressTestState = { 23 | stresses: {}, 24 | activeStress: '', 25 | currentRunStressId: '', 26 | currentRunStressName: '', 27 | workerInfos: [], 28 | tasks: [] 29 | }; -------------------------------------------------------------------------------- /api/src/models/record_doc.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryColumn, Column, UpdateDateColumn, CreateDateColumn, OneToOne, OneToMany } from 'typeorm'; 2 | import { Record } from './record'; 3 | import { RecordDocHistory } from './record_doc_history'; 4 | 5 | @Entity() 6 | export class RecordDoc { 7 | 8 | @PrimaryColumn() 9 | id: string; 10 | 11 | @OneToOne(_type => Record, record => record.doc) 12 | record: Record; 13 | 14 | @OneToMany(_type => RecordDocHistory, history => history.target) 15 | history: RecordDocHistory[]; 16 | 17 | @Column({ nullable: true }) 18 | version: number; // TODO: need increase for each changing 19 | 20 | @CreateDateColumn() 21 | createDate: Date; 22 | 23 | @UpdateDateColumn() 24 | updateDate: Date; 25 | } -------------------------------------------------------------------------------- /api/src/services/base/web_socket_handler.ts: -------------------------------------------------------------------------------- 1 | import * as WS from 'ws'; 2 | import { Log } from '../../utils/log'; 3 | 4 | export abstract class WebSocketHandler { 5 | 6 | protected socket: WS; 7 | 8 | handle = (socket: WS) => { 9 | this.socket = socket; 10 | this.init(); 11 | socket.on('message', data => this.onReceive(data as string)); 12 | socket.on('close', () => this.onClose()); 13 | } 14 | 15 | init() { 16 | Log.info('ws init'); 17 | } 18 | 19 | abstract onReceive(data: string); 20 | 21 | abstract onClose(); 22 | 23 | send = (data: string) => { 24 | this.socket.send(data); 25 | } 26 | 27 | close = (data?: string) => { 28 | this.socket.close(1000, data); 29 | } 30 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_run_result.ts: -------------------------------------------------------------------------------- 1 | import { DtoError } from './dto_error'; 2 | import { Duration } from './dto_stress_setting'; 3 | import { ConsoleMsg } from './dto_res'; 4 | 5 | export interface RunResult { 6 | 7 | id: string; 8 | 9 | envId: string; 10 | 11 | param?: string; 12 | 13 | error?: DtoError; 14 | 15 | body: any; 16 | 17 | tests: { [key: string]: boolean }; 18 | 19 | variables: {}; 20 | 21 | export: {}; 22 | 23 | status: number; 24 | 25 | statusMessage: string; 26 | 27 | elapsed: number; 28 | 29 | duration?: Duration; 30 | 31 | headers: { [key: string]: string | string[] }; 32 | 33 | cookies: string[]; 34 | 35 | host: string; 36 | 37 | consoleMsgQueue: ConsoleMsg[]; 38 | } -------------------------------------------------------------------------------- /api/src/utils/log.ts: -------------------------------------------------------------------------------- 1 | import * as Log4js from 'log4js'; 2 | import { Logger, getLogger } from 'log4js'; 3 | import * as Path from 'path'; 4 | 5 | export class Log { 6 | 7 | private static logger: Logger; 8 | 9 | static init() { 10 | Log4js.configure(Path.join(__dirname, '../../logconfig.json')); 11 | Log.logger = getLogger('default'); 12 | Log.logger.setLevel(Log4js.levels.DEBUG); 13 | } 14 | 15 | static info(info: string) { 16 | Log.logger.info(info); 17 | } 18 | 19 | static debug(debug: string) { 20 | Log.logger.debug(debug); 21 | } 22 | 23 | static warn(warn: string) { 24 | Log.logger.warn(warn); 25 | } 26 | 27 | static error(error: string) { 28 | Log.logger.error(error); 29 | } 30 | } -------------------------------------------------------------------------------- /en/Stress/Create_Stress.md: -------------------------------------------------------------------------------- 1 | Go to Stress test Module and click `create stress test` button. 2 | 3 | Pop a new stress test dialog: 4 | 5 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stress/stress_create.png) 6 | 7 | **Name**: name of this stress test. 8 | 9 | **Collection**: select a collection which you want to run. 10 | 11 | **Requests**: Filter and Sort requests. 12 | 13 | **Repeat**: repeat times. 14 | 15 | **Concurrency**: concurrency. 16 | 17 | **QPS**: query per second for a worker, default is 0 (no limit). 18 | 19 | **Timeout**: timeout (second),default is 0 (worker never timeout). 20 | 21 | **Keeplive**: set Keeplive for each request. 22 | 23 | **Environment**: request will run in this environment. 24 | 25 | Hit OK button to create a stress test. -------------------------------------------------------------------------------- /api/src/models/environment.ts: -------------------------------------------------------------------------------- 1 | import { ManyToOne, OneToMany, Entity, PrimaryColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; 2 | import { Variable } from './variable'; 3 | import { Project } from './project'; 4 | 5 | @Entity() 6 | export class Environment { 7 | @PrimaryColumn() 8 | id: string; 9 | 10 | @Column() 11 | name: string; 12 | 13 | @OneToMany(_type => Variable, variable => variable.environment, { 14 | cascadeInsert: true, 15 | cascadeUpdate: true 16 | }) 17 | variables: Variable[] = []; 18 | 19 | @ManyToOne(_type => Project, project => project.environments) 20 | project: Project; 21 | 22 | @CreateDateColumn() 23 | createDate: Date; 24 | 25 | @UpdateDateColumn() 26 | updateDate: Date; 27 | } 28 | -------------------------------------------------------------------------------- /cn/Script/README.md: -------------------------------------------------------------------------------- 1 | Hitchhiker 拥有一个超强的脚本系统,你可以在请求发送前或发送后对请求进行改造,可以在在请求前引入一些库,比如加密库来做一个准备工作。 2 | 3 | Hitchhiker 不仅支持一些内置的脚本库,强大之外在于支持上传脚本库,也就是说只要nodejs能做的,这里都可以做,甚至操作数据库都没有问题。 4 | 5 | Hitchhiker有三个级别的脚本: 6 | 7 | 1. Project级别的 [Global function](Global_Func.md). 8 | 9 | 2. Collection级别的 [Common Pre Request Script](Common_Pre_Script.md). 10 | 11 | 3. Request级别的 [Pre Request Script](Pre_Script.md) 以及 [Test Script](Test.md). 12 | 13 | 脚本都是用Javascript来写的,支持ES6和部分ES7,取诀于Server的Nodejs版本支持情况 (压力测试不支持ES6以及Pre Request Script,会尽快支持) 14 | 15 | 脚本的API参考:[脚本API](API.md). 16 | 17 | 自定义脚本库参考:[自定义脚本库](custom-javascript-lib.md). 18 | 19 | 预定义的数据参考:[自定义数据文件](custom-data-file.md). 20 | 21 | 下面这幅图展示了请求从开始到结束,整个过程脚本、变量的应用 22 | 23 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/reuqest_wf.png) -------------------------------------------------------------------------------- /client/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { store } from './store'; 5 | import * as l from './locales/lang'; 6 | import App from './App'; 7 | import LocalStore from './utils/local_store'; 8 | import { addLocaleData, IntlProvider } from 'react-intl'; 9 | import { GlobalVar } from './utils/global_var'; 10 | 11 | const appLocale = l.language[GlobalVar.instance.lang] || l.language['en']; 12 | 13 | addLocaleData(appLocale.data); 14 | 15 | LocalStore.init(); 16 | 17 | ReactDOM.render( 18 | 19 | 20 | 21 | 22 | , 23 | document.getElementById('root') as HTMLElement 24 | ); -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_collection.ts: -------------------------------------------------------------------------------- 1 | import { DtoRecord } from './dto_record'; 2 | import { DtoHeader } from './dto_header'; 3 | import * as _ from 'lodash'; 4 | 5 | export interface DtoCollection { 6 | 7 | id: string; 8 | 9 | name: string; 10 | 11 | commonPreScript: string; 12 | 13 | reqStrictSSL?: boolean; 14 | 15 | reqFollowRedirect?: boolean; 16 | 17 | projectId: string; 18 | 19 | commonSetting: DtoCommonSetting; 20 | 21 | description: string; 22 | } 23 | 24 | export interface DtoCommonSetting { 25 | 26 | prescript: string; 27 | 28 | test: string; 29 | 30 | headers: DtoHeader[]; 31 | } 32 | 33 | export interface DtoCollectionWithRecord { 34 | 35 | collections: _.Dictionary; 36 | 37 | records: _.Dictionary<_.Dictionary>; 38 | } -------------------------------------------------------------------------------- /client/src/common/enum/stress_type.ts: -------------------------------------------------------------------------------- 1 | export const noEnvironment = 'No environment'; 2 | 3 | export enum WorkerStatus { 4 | 5 | idle = 0, 6 | 7 | ready = 1, 8 | 9 | working = 2, 10 | 11 | finish = 3, 12 | 13 | down = 4, 14 | 15 | fileReady = 5, 16 | } 17 | 18 | export enum StressMessageType { 19 | 20 | hardware = 0, 21 | 22 | task = 1, 23 | 24 | start = 2, 25 | 26 | runResult = 3, 27 | 28 | stop = 4, 29 | 30 | status = 5, 31 | 32 | fileStart = 6, 33 | 34 | fileFinish = 7, 35 | 36 | init, 37 | 38 | close, 39 | 40 | wait, 41 | 42 | error, 43 | 44 | finish, 45 | 46 | noWorker 47 | } 48 | 49 | export class StressFailedType { 50 | 51 | static noRes = 'noRes'; 52 | 53 | static m500 = 'm500'; 54 | 55 | static testFailed = 'testFailed'; 56 | } -------------------------------------------------------------------------------- /en/Variable/Dynamic_Var.md: -------------------------------------------------------------------------------- 1 | Runtime variable is defined in Script, usually it's used to generate hash value dynamically for url or save a request's response and use it in another request. 2 | 3 | Runtime variable can be defined in `Global Function`, `Common Pre Request Script`, `Pre Request Script` and `Test`. 4 | 5 | Use this API as below: 6 | 7 | ```js 8 | hitchhiker.setEnvVariable('rt_var', 'test'); // define a variable with key: `rt_var`, value: `test` 9 | const value = hitchhiker.getEnvVariable('rt_var'); // get variable's value (`test`) 10 | ``` 11 | 12 | A typical scenario: 13 | 14 | We have 4 APIs: `create`, `select`, `update`, `delete`. 15 | `create` API create a item and return id. 16 | we need this id to test the 3 other APIs, so we can set id to a Runtime variable in Test script of `create` and use it in `select`, `update`, `delete`. 17 | -------------------------------------------------------------------------------- /api/src/services/console_message.ts: -------------------------------------------------------------------------------- 1 | import { ConsoleMsg } from '../common/interfaces/dto_res'; 2 | 3 | export class ConsoleMessage { 4 | 5 | private valid: boolean; 6 | 7 | private msgs: ConsoleMsg[] = []; 8 | 9 | get messages() { 10 | return this.msgs; 11 | } 12 | 13 | static create(valid: boolean) { 14 | var cm = new ConsoleMessage(); 15 | cm.valid = valid; 16 | return cm; 17 | } 18 | 19 | push(message: string, type: string = 'info', force?: boolean) { 20 | if (force || this.valid) { 21 | this.msgs.push({ time: new Date(), message, type, custom: false }); 22 | } 23 | } 24 | 25 | pushArray(msgs: ConsoleMsg[], _isCustom?: boolean, force?: boolean) { 26 | if (force || this.valid) { 27 | this.msgs.push(...msgs); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /api/src/run_engine/process/stress_nodejs_worker_handler.ts: -------------------------------------------------------------------------------- 1 | import { BaseProcessHandler } from './base_process_handler'; 2 | import { StressMessageType } from '../../common/enum/stress_type'; 3 | import { Log } from '../../utils/log'; 4 | 5 | export class StressNodejsWorkerHandler extends BaseProcessHandler { 6 | 7 | isFinish: boolean; 8 | 9 | handleMessage(msg: any) { 10 | Log.info(`stress nodejs worker handle msg`); 11 | if (msg === 'ready') { 12 | this.process.send({ type: StressMessageType.start }); 13 | } else if (msg === 'finish' || msg === 'error') { 14 | this.isFinish = true; 15 | } 16 | 17 | if (this.call) { 18 | this.call(msg); 19 | } 20 | } 21 | 22 | afterProcessCreated() { 23 | Log.info(`stress nodejs worker process created`); 24 | } 25 | } -------------------------------------------------------------------------------- /cn/Simple_Tutorial/Create_Collection.md: -------------------------------------------------------------------------------- 1 | #### 创建一个collection 2 | 3 | 现在我们有了一个Project,可以开始在这个Project下面创建Collection了。 4 | 5 | Collection 可以说是很多请求的集合,在Collection下面还可以创建文件夹,然后把Request组织起来。 6 | 7 | Collection同时也是Schedule和Stress Test跑的单位。 8 | 9 | 创建一个Collection步骤: 10 | 11 | 1. 进入Collection模块. 12 | 13 | 2. 单击`create collection`按钮 14 | 15 | 3. 在对话框里输入名字 `Sample Collection` 然后选择前面我们创建的Project `SampleAPI` 16 | 17 | 4. 点击OK,完成创建 18 | 19 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/simple_tutorial/collection_create.png) 20 | 21 | Collection创建好后,同Project的成员都可以看到这个新创建的Collection。 22 | 23 | #### Collection 菜单功能 24 | 25 | 1. 创建文件夹 26 | 27 | 2. Common Pre Request Script, 参考 [脚本](../Script/Common_Pre_Script.md) 28 | 29 | 3. Request Strict SSL: 勾上的话,这个Collection下的所有请求都会验证SSL证书 30 | 31 | 4. Request Follow Redirect: 勾上的话,所有请求的3xx返回都会跟随自动跳转的请求。 -------------------------------------------------------------------------------- /api/src/models/header.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, ManyToOne, PrimaryColumn } from 'typeorm'; 2 | import { Record } from './record'; 3 | import { Mock } from './mock'; 4 | import { MockCollection } from './mock_collection'; 5 | 6 | @Entity() 7 | export class Header { 8 | @PrimaryColumn() 9 | id: string; 10 | 11 | @Column({ nullable: true }) 12 | key: string; 13 | 14 | @Column('text', { nullable: true }) 15 | value: string; 16 | 17 | @Column({ default: true }) 18 | isActive: boolean; 19 | 20 | @Column({ default: false }) 21 | isFav: boolean; 22 | 23 | @Column() 24 | sort: number; 25 | 26 | @Column('text', { nullable: true }) 27 | description: string; 28 | 29 | @ManyToOne(_type => Record, record => record.id) 30 | record: Record; 31 | 32 | @ManyToOne(_type => Mock, mock => mock.id) 33 | mock?: Mock; 34 | } -------------------------------------------------------------------------------- /en/Variable/README.md: -------------------------------------------------------------------------------- 1 | Variable is very useful for these cases: 2 | 3 | 1. Request in multiple environment. 4 | 5 | 2. Request has many dynamic parameters in url query or body. 6 | 7 | 3. A request depend on one or more requests. 8 | 9 | 4. Need change request's url/headers/body before sending it. 10 | 11 | Variable is always a key-value object and use with a uniform format: `{{key}}`, you can use variable in `Common Pre Request Script`, `Pre Request Script`, `Test`, `Url`, `Body` and `Header`. 12 | 13 | All `{{key}}` will be replace to `Value` of the variable before or after sending reqeust. Variable transfer workflow reference to: [Variable Transfer Workflow](../Script/README.md). 14 | 15 | Hitchhiker have three types of variable. 16 | 17 | 1. [Environment variable](Env_Var.md) 18 | 19 | 2. [Parameter variable](Param_Var.md) 20 | 21 | 3. [Runtime variable](Dynamic_Var.md) -------------------------------------------------------------------------------- /api/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as Koa from 'koa'; 2 | import Middleware from './middlewares/middleware'; 3 | import { Log } from './utils/log'; 4 | import { ChildProcessManager } from './run_engine/process/child_process_manager'; 5 | import 'reflect-metadata'; 6 | import { WebSocketService } from './services/web_socket_service'; 7 | import { Setting } from './utils/setting'; 8 | import { ProjectDataService } from './services/project_data_service'; 9 | 10 | let app = new Koa(); 11 | 12 | Log.init(); 13 | 14 | process.on('uncaughtException', (err) => { 15 | Log.error(err); 16 | }); 17 | 18 | Setting.instance.init(); 19 | 20 | ProjectDataService.instance.init(); 21 | 22 | ChildProcessManager.default.init(); 23 | 24 | app.use(Middleware()); 25 | 26 | const server = app.listen(Setting.instance.appPort); 27 | 28 | server.timeout = 30 * 60 * 1000; 29 | 30 | new WebSocketService(server).start(); -------------------------------------------------------------------------------- /api/src/run_engine/process/schedule_process.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import { Setting } from '../../utils/setting'; 3 | import { Log } from '../../utils/log'; 4 | import { ScheduleRunner } from '../schedule_runner'; 5 | import { ProjectDataService } from '../../services/project_data_service'; 6 | 7 | Log.init(); 8 | 9 | process.on('uncaughtException', (err) => { 10 | Log.error(err); 11 | }); 12 | 13 | process.on('message', (msg) => { 14 | if (msg === 'start') { 15 | startScheduleProcess(); 16 | } else if (msg === 'reload_project_data') { 17 | Log.info('schedule: reload libs'); 18 | ProjectDataService.instance.reload(); 19 | } 20 | }); 21 | 22 | function startScheduleProcess() { 23 | new ScheduleRunner().run(); 24 | setInterval(() => { 25 | new ScheduleRunner().run(); 26 | }, Setting.instance.scheduleDuration * 1000); 27 | } -------------------------------------------------------------------------------- /client/src/action/ui.ts: -------------------------------------------------------------------------------- 1 | export const SelectReqTabType = 'select req panel tab'; 2 | 3 | export const SelectResTabType = 'select res panel tab'; 4 | 5 | export const ToggleReqPanelVisibleType = 'toggle req panel visible'; 6 | 7 | export const ResizeResHeightType = 'resize res panel height'; 8 | 9 | export const ResizeLeftPanelType = 'resize left panel'; 10 | 11 | export const UpdateLeftPanelType = 'collapse left panel'; 12 | 13 | export const SwitchHeadersEditModeType = 'switch headers edit mode'; 14 | 15 | export const ShowTimelineType = 'show timeline'; 16 | 17 | export const CloseTimelineType = 'close timeline'; 18 | 19 | export const DisplayQueryStringType = 'display query string'; 20 | 21 | export const ToggleRequestDescriptionType = 'toggle request description'; 22 | 23 | export const BatchCloseType = 'batch close type'; 24 | 25 | export const TableDisplayType = 'table display type'; -------------------------------------------------------------------------------- /client/src/common/utils/math_util.ts: -------------------------------------------------------------------------------- 1 | export class MathUtil { 2 | 3 | static distribute(total: number, cores: number): number[] { 4 | const result: number[] = []; 5 | let distributed = 0; 6 | for (let i = 0; i < cores; i++) { 7 | const max = parseInt((total * (i + 1) / cores) + ''); 8 | const current = i === 0 ? max : max - distributed; 9 | distributed += current; 10 | result.push(current); 11 | } 12 | return result; 13 | } 14 | 15 | static stddev(data: number[]): number { 16 | if (!data || data.length === 0) { 17 | return 0; 18 | } 19 | var mean = data.reduce((p, c) => p + c, 0) / data.length; 20 | var deviations = data.map(x => x - mean); 21 | return Math.sqrt(deviations.map(x => x * x).reduce((p, c) => p + c, 0) / data.length); 22 | } 23 | } -------------------------------------------------------------------------------- /deploy/docker/private/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | hitchhiker: 4 | image: hitchhiker 5 | container_name: hitchhiker 6 | environment: 7 | - HITCHHIKER_DB_HOST=hitchhiker-mysql 8 | ports: 9 | - "8080:8080" 10 | - "11010:11010" 11 | volumes: 12 | - /my/hitchhiker/public:/usr/src/Hitchhiker/build/public 13 | - /my/hitchhiker/data:/usr/src/Hitchhiker/build/global_data/project 14 | - /my/hitchhiker/backup:/usr/src/Hitchhiker/build/backup 15 | - /my/hitchhiker/logs:/usr/src/Hitchhiker/build/logs 16 | links: 17 | - hitchhiker-mysql 18 | hitchhiker-mysql: 19 | image: mysql:5.7 20 | container_name: hitchhiker-mysql 21 | environment: 22 | - MYSQL_DATABASE=hitchhiker 23 | volumes: 24 | - ./hitchhiker.conf:/etc/mysql/conf.d/hitchhiker.conf 25 | - /my/hitchhiker/sqldata:/var/lib/mysql -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | bin 40 | typings 41 | build 42 | ~$test case.xlsx 43 | staging 44 | 45 | # symbolic link folder 46 | api/src/common 47 | 48 | .awcache 49 | stats.json -------------------------------------------------------------------------------- /en/Stress/Run.md: -------------------------------------------------------------------------------- 1 | If you have a running Stress Node already, you can run stress test right now. 2 | 3 | Move mouse to Stress test item and click menu item `Run Now`. 4 | 5 | Hitchhiker will display real-time state of stress test task, include workers, request progress, duration and failed status. 6 | 7 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stress/stress_run.png) 8 | 9 | There are three diagram: 10 | 11 | 1. Run progress, include done count, total count, and TPS. 12 | 13 | 2. Duration of each request, include DNS, Connect, Request, Min, Max 14 | 15 | 3. Request failed status, include No Response, Server Error(500), Test Failed. 16 | 17 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stresstest.gif) 18 | 19 | You can stop the running stress test. 20 | 21 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/stress/stop.png) -------------------------------------------------------------------------------- /api/src/models/schedule_record.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn } from 'typeorm'; 2 | import { Schedule } from './schedule'; 3 | import { RunResult } from '../common/interfaces/dto_run_result'; 4 | 5 | @Entity() 6 | export class ScheduleRecord { 7 | 8 | @PrimaryGeneratedColumn() 9 | id: number; 10 | 11 | @ManyToOne(_type => Schedule, schedule => schedule.scheduleRecords) 12 | schedule: Schedule; 13 | 14 | @Column() 15 | duration: number; 16 | 17 | @Column('json') 18 | result: { origin: Array>, compare: Array> }; 19 | 20 | @Column() 21 | success: boolean; 22 | 23 | @Column() 24 | isScheduleRun: boolean; 25 | 26 | @Column({ default: () => `'1949-10-01'` }) 27 | runDate: Date; 28 | 29 | @CreateDateColumn() 30 | createDate: Date; 31 | } -------------------------------------------------------------------------------- /client/config/jest/typescriptTransform.js: -------------------------------------------------------------------------------- 1 | // Copyright 2004-present Facebook. All Rights Reserved. 2 | 3 | const fs = require('fs'); 4 | const tsc = require('typescript'); 5 | const tsconfigPath = require('app-root-path').resolve('/tsconfig.json'); 6 | 7 | let compilerConfig = { 8 | module: tsc.ModuleKind.CommonJS, 9 | jsx: tsc.JsxEmit.React, 10 | } 11 | 12 | if (fs.existsSync(tsconfigPath)) { 13 | try { 14 | const tsconfig = require(tsconfigPath); 15 | 16 | if (tsconfig && tsconfig.compilerOptions) { 17 | compilerConfig = tsconfig.compilerOptions; 18 | } 19 | } catch (e) { /* Do nothing - default is set */ } 20 | } 21 | 22 | module.exports = { 23 | process(src, path) { 24 | if (path.endsWith('.ts') || path.endsWith('.tsx')) { 25 | return tsc.transpile( 26 | src, 27 | compilerConfig, 28 | path, [] 29 | ); 30 | } 31 | return src; 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /client/src/common/utils/date_util.ts: -------------------------------------------------------------------------------- 1 | export class DateUtil { 2 | 3 | static readonly MINUTE = 60 * 1000; 4 | 5 | static readonly HOUR = 60 * DateUtil.MINUTE; 6 | 7 | static readonly DAY = 24 * DateUtil.HOUR; 8 | 9 | static diff(start: Date, end: Date, unit: 'h' | 'm' = 'h', offset: number = 0): number { 10 | const timeDiff = Math.abs(end.getTime() - start.getTime() + offset); 11 | return parseInt((timeDiff / (unit === 'h' ? DateUtil.HOUR : DateUtil.MINUTE)) + ''); 12 | } 13 | 14 | static getUTCDate(date?: Date): Date { 15 | date = date || new Date(); 16 | return new Date( 17 | date.getUTCFullYear(), 18 | date.getUTCMonth(), 19 | date.getUTCDate(), 20 | date.getUTCHours(), 21 | date.getUTCMinutes(), 22 | date.getUTCSeconds(), 23 | date.getUTCMilliseconds() 24 | ); 25 | } 26 | } -------------------------------------------------------------------------------- /api/src/controllers/environment_controller.ts: -------------------------------------------------------------------------------- 1 | import { POST, DELETE, PUT, PathParam, BodyParam, BaseController } from 'webapi-router'; 2 | import { ResObject } from '../interfaces/res_object'; 3 | import { EnvironmentService } from '../services/environment_service'; 4 | import { DtoEnvironment } from '../common/interfaces/dto_environment'; 5 | 6 | export default class EnvironmentController extends BaseController { 7 | 8 | @POST('/environment') 9 | async create(@BodyParam env: DtoEnvironment): Promise { 10 | return await EnvironmentService.create(env); 11 | } 12 | 13 | @PUT('/environment') 14 | async update(@BodyParam env: DtoEnvironment): Promise { 15 | return await EnvironmentService.update(env); 16 | } 17 | 18 | @DELETE('/environment/:id') 19 | async delete(@PathParam('id') id: string) { 20 | return await EnvironmentService.delete(id); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /client/src/locales/string.ts: -------------------------------------------------------------------------------- 1 | import { GlobalVar } from '../utils/global_var'; 2 | import zhMessages from './zh.json'; 3 | import enMessages from './en.json'; 4 | 5 | export default class LocalesString { 6 | 7 | static intl: any; 8 | 9 | static enMessages = enMessages; 10 | 11 | static zhMessages = zhMessages; 12 | 13 | static get(id: string, values?: {}) { 14 | if (!this.intl) { 15 | return this.getSpecial(id, values); 16 | } 17 | return this.intl.formatMessage({ id }, values); 18 | } 19 | 20 | static getSpecial(id: string, values?: {}) { 21 | let content = (this[`${GlobalVar.instance.lang}Messages`] || this['enMessages'])[id]; 22 | if (values !== undefined) { 23 | Object.keys(values).forEach(k => { 24 | content = content.replace(`{${k}}`, values[k]); 25 | }); 26 | } 27 | return content; 28 | } 29 | } -------------------------------------------------------------------------------- /en/Schedule/Create_Schedule.md: -------------------------------------------------------------------------------- 1 | Go to Schedule module, click `create schedule` button to create a schedule. 2 | 3 | Pop a new schedule dialog: 4 | 5 | **Name**: name of this schedule. 6 | 7 | **Collection**: select a collection which you want to run. 8 | 9 | **Sort request**: run requests in order, if this option is checked, a requests panel will be displayed and you can drag and drop the request items to sort them. 10 | 11 | **Period**: collection will be run in this period, collection could be run every day/hour/minute. 12 | 13 | **Environment**: collection will be run with this environment. 14 | 15 | **Compare**: comparison will be enable if this option is checked, yo can select a reference environment. 16 | 17 | **Notification**: send email to peoples after collection run completely. 18 | 19 | Hit OK button to create a schedule. 20 | 21 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/schedule/schedule_create.png) 22 | 23 | -------------------------------------------------------------------------------- /client/src/reducer/local_data.ts: -------------------------------------------------------------------------------- 1 | import { LocalDataState, localDataDefaultValue } from '../state/local_data'; 2 | import { FetchLocalDataSuccessType, FetchLocalDataFailedType, FetchLocalDataPendingType } from '../action/local_data'; 3 | import { RequestStatus } from '../misc/request_status'; 4 | 5 | export function localDataState(state: LocalDataState = localDataDefaultValue, action: any): LocalDataState { 6 | switch (action.type) { 7 | case FetchLocalDataSuccessType: { 8 | return { ...state, fetchLocalDataState: { status: RequestStatus.success } }; 9 | } 10 | case FetchLocalDataPendingType: { 11 | return { ...state, fetchLocalDataState: { status: RequestStatus.pending } }; 12 | } 13 | case FetchLocalDataFailedType: { 14 | return { ...state, fetchLocalDataState: { status: RequestStatus.failed, message: action.value } }; 15 | } 16 | default: 17 | return state; 18 | } 19 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/dto_schedule.ts: -------------------------------------------------------------------------------- 1 | import { DtoScheduleRecord } from './dto_schedule_record'; 2 | import { Period, TimerType } from '../enum/period'; 3 | import { NotificationMode, MailMode } from '../enum/notification_mode'; 4 | 5 | export interface DtoSchedule { 6 | 7 | id: string; 8 | 9 | name: string; 10 | 11 | collectionId: string; 12 | 13 | environmentId: string; 14 | 15 | needCompare: boolean; 16 | 17 | compareEnvironmentId: string; 18 | 19 | timer: TimerType; 20 | 21 | period: Period; 22 | 23 | hour: number; 24 | 25 | notification: NotificationMode; 26 | 27 | emails?: string; 28 | 29 | mailMode: MailMode; 30 | 31 | mailIncludeSuccessReq: boolean; 32 | 33 | needOrder: boolean; 34 | 35 | recordsOrder: string; 36 | 37 | suspend: boolean; 38 | 39 | scheduleRecords: DtoScheduleRecord[]; 40 | 41 | ownerId: string; 42 | 43 | lastRunDate?: Date; 44 | 45 | recordCount: number; 46 | } -------------------------------------------------------------------------------- /deploy/linux_deploy.sh: -------------------------------------------------------------------------------- 1 | # ** 如果您打算部署Hitchhiker,请忽略这个脚本,部署请参考:http://doc.hitchhiker-api.com/cn/installation/ 如果是开发,可以参考脚本搭开发环境 2 | # ** if you try to deploy Hitchhiker, please ignore this file, reference to: http://doc.hitchhiker-api.com/cn/installation/ 3 | # ensure mysql is installed with user:'root' password: 'hitchhiker888' and create database 'hitchhiker-prod' 4 | # replace myhost with your ip, keep 8080 port 5 | 6 | myhost="http://10.86.18.215:8080/" 7 | 8 | export NODE_ENV="develop" 9 | 10 | git clone -b release https://github.com/brookshi/Hitchhiker.git 11 | cd ./Hitchhiker 12 | npm install -g pm2 yarn gulp-cli typescript@2.3.3 13 | npm install gulp -D 14 | npm install typescript@2.3.3 --save 15 | npm install 16 | cd ./client 17 | npm install 18 | cd .. 19 | 20 | export NODE_ENV="production" 21 | 22 | gulp release --prod 23 | 24 | ## for BSD/OSX use : sed -i '' "s#myhost#$myhost#g" pm2.json 25 | sed -i "s#myhost#$myhost#g" pm2.json 26 | 27 | sleep 10s 28 | 29 | pm2 start ./pm2.json -------------------------------------------------------------------------------- /client/src/state/schedule.ts: -------------------------------------------------------------------------------- 1 | import { DtoSchedule } from '../common/interfaces/dto_schedule'; 2 | import { RunResult } from '../common/interfaces/dto_run_result'; 3 | import { ScheduleRecordsDisplayMode } from '../misc/custom_type'; 4 | import * as _ from 'lodash'; 5 | 6 | export interface ScheduleState { 7 | 8 | schedules: _.Dictionary; 9 | 10 | activeSchedule: string; 11 | 12 | runState: _.Dictionary; 13 | 14 | scheduleRecordsInfo: _.Dictionary; 15 | } 16 | 17 | export interface ScheduleRecordsInfo { 18 | 19 | pageNum: number; 20 | 21 | mode: ScheduleRecordsDisplayMode; 22 | 23 | excludeNotExist: boolean; 24 | } 25 | 26 | export interface ScheduleRunState { 27 | 28 | isRunning: boolean; 29 | 30 | consoleRunResults: RunResult[]; 31 | } 32 | 33 | export const scheduleDefaultValue: ScheduleState = { 34 | schedules: {}, 35 | activeSchedule: '', 36 | runState: {}, 37 | scheduleRecordsInfo: {} 38 | }; -------------------------------------------------------------------------------- /en/Script/Test.md: -------------------------------------------------------------------------------- 1 | Test script allows you write test case in it, you can verify the response is correctly. 2 | 3 | Test script will be executed after the request is back, in Test script, you can get some build-in variable: 4 | 5 | ``` javascript 6 | `responseBody`: the response's body 7 | `responseObj`:json object of this response's body 8 | `responseHeaders`: response's headers 9 | `responseTime`: request elapse time (ms) 10 | `responseCode.code`: response status 11 | `responseCode.name`: response message 12 | ``` 13 | 14 | You can verify data like this: 15 | ```js 16 | tests["value is correct"] = responseObj.value === 100; 17 | 18 | tests["status code is 200"] = responseCode.code === 200; 19 | ``` 20 | 21 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/test_result.png) 22 | 23 | You also can save response as a file to server, then can load this file in another request. 24 | 25 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/script_test.png) -------------------------------------------------------------------------------- /en/Script/README.md: -------------------------------------------------------------------------------- 1 | Hitchhiker has a powerful runtime script that allows you to adjust request before/after sending. You can prepare some data for request and add test case to verify response. 2 | 3 | There are 3 level script: 4 | 5 | 1. [Global function](Global_Func.md) in Project level. 6 | 7 | 2. [Common Pre Request Script](Common_Pre_Script.md) in Collection level. 8 | 9 | 3. [Pre Request Script](Pre_Script.md) and [Test Script](Test.md) in Request level. 10 | 11 | Scripts are written in Javascript, support ES6. (Stress Test doesn't support ES6 and Pre Request Script right now, will be fixed at once) 12 | 13 | Script API reference to: [Script API](API.md). 14 | 15 | Custom javascript lib refer to: [Custom Javascript Lib](custom-javascript-lib.md). 16 | 17 | Custom data file refer to: [Custom Data File](custom-data-file.md). 18 | 19 | Below is workflow of Hitchhiker's request included script and variable. 20 | 21 | 22 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/script/reuqest_wf.png) -------------------------------------------------------------------------------- /client/src/state/user.ts: -------------------------------------------------------------------------------- 1 | import { DtoResUser } from '../common/interfaces/dto_res'; 2 | import { RequestState, requestStateDefaultValue } from './request'; 3 | 4 | export interface UserInfoState { 5 | 6 | lastLoginName: string; 7 | 8 | userInfo: DtoResUser; 9 | 10 | loginState: RequestState; 11 | 12 | registerState: RequestState; 13 | 14 | findPasswordState: RequestState; 15 | 16 | changePasswordState: RequestState; 17 | } 18 | 19 | export const userInfoDefaultValue: UserInfoState = { 20 | lastLoginName: '', 21 | userInfo: { 22 | projects: [], 23 | id: '', 24 | name: '', 25 | password: '', 26 | email: '', 27 | isTemp: false, 28 | isActive: true, 29 | createDate: new Date(), 30 | updateDate: new Date(), 31 | }, 32 | loginState: requestStateDefaultValue, 33 | registerState: requestStateDefaultValue, 34 | findPasswordState: requestStateDefaultValue, 35 | changePasswordState: requestStateDefaultValue 36 | }; -------------------------------------------------------------------------------- /client/src/utils/local_store.ts: -------------------------------------------------------------------------------- 1 | import localForage from 'localforage'; 2 | import { State } from '../state/index'; 3 | 4 | export default class LocalStore { 5 | 6 | static init() { 7 | localForage.config({ 8 | name: 'hitchhiker', 9 | version: 1.0, 10 | storeName: 'localState' 11 | }); 12 | } 13 | 14 | static async setState(userId: string, state: State): Promise { 15 | await localForage.setItem(LocalStore.getKey(userId), state); 16 | } 17 | 18 | static async getState(userId: string): Promise { 19 | return await localForage.getItem(LocalStore.getKey(userId)) as State; 20 | } 21 | 22 | static async clearState(userId: string): Promise { 23 | console.log(LocalStore.getKey(userId)); 24 | return await localForage.removeItem(LocalStore.getKey(userId), err => console.error(err)); 25 | } 26 | 27 | private static getKey(userId: string): string { 28 | return `state-${userId}`; 29 | } 30 | } -------------------------------------------------------------------------------- /client/src/common/enum/notification_mode.ts: -------------------------------------------------------------------------------- 1 | export enum NotificationMode { 2 | 3 | none = 0, 4 | 5 | me = 1, 6 | 7 | project = 2, 8 | 9 | custom = 3, 10 | } 11 | 12 | export class NotificationStr { 13 | 14 | static none = 'None'; 15 | 16 | static me = 'Me'; 17 | 18 | static project = 'Project Members'; 19 | 20 | static custom = 'Custom'; 21 | 22 | static convert(mode: NotificationMode) { 23 | switch (mode) { 24 | case NotificationMode.none: 25 | return NotificationStr.none; 26 | case NotificationMode.me: 27 | return NotificationStr.me; 28 | case NotificationMode.project: 29 | return NotificationStr.project; 30 | case NotificationMode.custom: 31 | return NotificationStr.custom; 32 | default: 33 | return NotificationStr.none; 34 | } 35 | } 36 | } 37 | 38 | export enum MailMode { 39 | 40 | mailAlways = 0, 41 | 42 | mailWhenFail = 1, 43 | } -------------------------------------------------------------------------------- /en/Variable/Env_Var.md: -------------------------------------------------------------------------------- 1 | Environment variable could be defined in Project's environment, usually, Environment variable is used to in url's host, query string and headers. 2 | 3 | For example: 4 | We have three environment for our API: QA, Stage, Product. Each of environment have different domain name: 5 | ``` 6 | QA: http://api-qa.sample.com/ 7 | Stage: http://api-stg.sample.com/ 8 | Product: http://api.sample.com/ 9 | ``` 10 | 11 | Without variable, we need create three request for these domains, it's too tedious. 12 | 13 | We could create a environment named QA and add a variable with key: `host`, value: `http://api-qa.sample.com/`. 14 | Duplicate it and do the same thing for Stage and Product. 15 | 16 | Now we get three environments, we can use it in one request. 17 | Create a request with url: `{{host}}/get`, select environment and hit Send button. 18 | 19 | `{{host}}` will be replaced by the variable's value of selected environment. So if select QA, request's url will be `http://api-qa.sample.com/`, and Stage will be `http://api-stg.sample.com/`. -------------------------------------------------------------------------------- /client/src/misc/code_snippet_type.ts: -------------------------------------------------------------------------------- 1 | export const CodeSnippetType = { 2 | c: { name: 'C (LibCurl)', types: ['libcurl'] }, 3 | csharp: { name: 'C# (RestSharp)', types: ['restsharp'] }, 4 | go: { name: 'Go', types: ['native'] }, 5 | java: { name: 'Java', types: ['okhttp', 'unirest'] }, 6 | javascript: { name: 'Javascript', types: ['jquery', 'xhr'] }, 7 | node: { name: 'Node', types: ['native', 'request', 'unirest'] }, 8 | objc: { name: 'Objective-C (NSURL)', types: ['nsurlsession'] }, 9 | ocaml: { name: 'OCaml (Cohttp)', types: ['cohttp'] }, 10 | php: { name: 'PHP', types: ['curl', 'http1', 'http2'] }, 11 | python: { name: 'Python', types: ['requests', 'python3'] }, 12 | ruby: { name: 'Ruby', types: ['native'] }, 13 | shell: { name: 'Shell', types: ['curl', 'httpie', 'wget'] }, 14 | swift: { name: 'Swift (NSURL)', types: ['nsurlsession'] } 15 | }; 16 | 17 | export type CodeSnippetLang = 'c' | 'csharp' | 'go' | 'java' | 'javascript' | 'node' | 'objc' | 'ocaml' | 'php' | 'python' | 'ruby' | 'shell' | 'swift'; -------------------------------------------------------------------------------- /deploy/docker/hitchhiker_and_mysql/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | hitchhiker: 4 | image: brookshi/hitchhiker:v0.14 5 | container_name: hitchhiker 6 | environment: 7 | - HITCHHIKER_DB_HOST=hitchhiker-mysql 8 | - HITCHHIKER_APP_HOST=http://localhost:8080/ # should change before deploying. 9 | # add environment variable to config 10 | ports: 11 | - "8080:8080" 12 | - "11010:11010" 13 | links: 14 | - hitchhiker-mysql:hitchhiker-mysql 15 | volumes: 16 | - /my/hitchhiker/data:/usr/src/Hitchhiker/build/global_data/project 17 | - /my/hitchhiker/backup:/usr/src/Hitchhiker/build/backup 18 | - /my/hitchhiker/logs:/usr/src/Hitchhiker/build/logs 19 | hitchhiker-mysql: 20 | image: mysql:5.7 21 | container_name: hitchhiker-mysql 22 | environment: 23 | - MYSQL_ROOT_PASSWORD=hitchhiker888 24 | - MYSQL_DATABASE=hitchhiker-prod 25 | volumes: 26 | - ./hitchhiker-mysql.cnf:/etc/mysql/conf.d/hitchhiker.cnf 27 | - /my/hitchhiker/sqldata:/var/lib/mysql -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "build/dist", 4 | "module": "es6", 5 | "target": "es6", 6 | "lib": [ 7 | "es6", 8 | "dom" 9 | ], 10 | "sourceMap": true, 11 | "allowJs": true, 12 | "jsx": "preserve", 13 | "moduleResolution": "node", 14 | "rootDir": "src", 15 | "forceConsistentCasingInFileNames": false, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noImplicitAny": false, 19 | "strictNullChecks": true, 20 | "suppressImplicitAnyIndexErrors": true, 21 | "noUnusedLocals": true, 22 | "allowSyntheticDefaultImports": true, 23 | "experimentalDecorators": true, 24 | "emitDecoratorMetadata": true, 25 | "resolveJsonModule": true, 26 | "esModuleInterop": true, 27 | }, 28 | "include": [ 29 | "./src/**/*" 30 | ], 31 | "exclude": [ 32 | "node_modules", 33 | "build", 34 | "scripts", 35 | "acceptance-tests", 36 | "webpack", 37 | "src/setupTests.ts", 38 | "../api" 39 | ], 40 | "types": [ 41 | "typePatches" 42 | ] 43 | } -------------------------------------------------------------------------------- /api/src/services/schedule_on_demand_service.ts: -------------------------------------------------------------------------------- 1 | import { ScheduleService } from './schedule_service'; 2 | import { ScheduleRunner } from '../run_engine/schedule_runner'; 3 | import { WebSocketHandler } from './base/web_socket_handler'; 4 | import { Log } from '../utils/log'; 5 | import { Message } from '../utils/message'; 6 | 7 | export class ScheduleOnDemandService extends WebSocketHandler { 8 | 9 | onReceive(data: string) { 10 | Log.info(`receive data: ${data}`); 11 | if (!data) { 12 | this.close('invalid schedule id'); 13 | } 14 | 15 | this.run(data).then(() => this.close()); 16 | } 17 | 18 | onClose() { 19 | Log.info('client close'); 20 | this.close(); 21 | } 22 | 23 | async run(id: string): Promise { 24 | const schedule = await ScheduleService.getById(id); 25 | if (!schedule) { 26 | this.close(Message.get('scheduleNotExist')); 27 | return; 28 | } 29 | 30 | await new ScheduleRunner().runSchedule(schedule, null, false, data => this.send(data)); 31 | } 32 | } -------------------------------------------------------------------------------- /client/src/misc/test_snippet.ts: -------------------------------------------------------------------------------- 1 | import LocalesString from '../locales/string'; 2 | 3 | export function getTestSnippets() { 4 | const testSnippets = { 5 | [LocalesString.get('Snippet.BodyEqualString')]: 'tests["body is correct"] = responseBody === "body...";', 6 | [LocalesString.get('Snippet.BodyContainsString')]: 'tests["body contains string"] = responseBody.indexOf("some string...") > -1;', 7 | [LocalesString.get('Snippet.BodyJsonObj')]: 'tests["value is correct"] = responseObj.value === 100;', 8 | [LocalesString.get('Snippet.ResponseHeaderCheck')]: 'tests["contains json header"] = responseHeaders["content-type"] === "application/json; charset=utf-8";', 9 | [LocalesString.get('Snippet.ResponseTimeLess200')]: 'tests["response time is less than 200ms"] = responseTime < 200;', 10 | [LocalesString.get('Snippet.ResponseStatusCheck')]: 'tests["status code is 200"] = responseCode.code === 200;', 11 | [LocalesString.get('Snippet.ResponseStatusName')]: 'tests["status code name is ok"] = responseCode.name === "OK";', 12 | }; 13 | 14 | return testSnippets; 15 | }; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report (提交Bug) 3 | about: File a bug to help us improve. 4 | 5 | --- 6 | 7 | **1. What went wrong `(哪里出错)`?** 8 | Please describe what went wrong (请描述bug的具体表现) 9 | 10 | **2. Steps to reproduce the problem `(重现步骤)`:** 11 | Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner (简洁清晰的重现步骤能够帮助我们更迅速地定位问题所在) 12 | 13 | **3. What is the expected behavior `(期望表现)`?** 14 | Please describe the expected behavior (请描述期望表现) 15 | 16 | **4. Did this work before `(之前是否正常)`?** 17 | [Yes, No, N/A] [是,否,之前没用过] 18 | 19 | **5. Environment `(环境)`:** 20 | - Hitchhiker Version (Hitchhiker版本): [e.g. V0.14] 21 | - Use Docker or Installation Package (使用Docker还是安装包): [e.g. Docker/Installation Package] 22 | - Node Version (If you use the installation package) (如果使用安装包的Node版本): [e.g. 8.11.3] 23 | - Server OS (部署的系统): [e.g. Ubuntu, Windows, MacOS] 24 | - Browser (浏览器): [e.g. Chrome, Firefox, Safari] 25 | 26 | **6. Additional Information (e.g. Screenshots, Links) `(补充信息,截图,链接等)`** 27 | Add any other context about the problem here (补充具体的上下文信息) 28 | -------------------------------------------------------------------------------- /api/src/interfaces/user_data.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../models/user'; 2 | import { Project } from '../models/project'; 3 | import { Environment } from '../models/environment'; 4 | import { DtoSchedule } from '../common/interfaces/dto_schedule'; 5 | import { DtoStress } from '../common/interfaces/dto_stress'; 6 | import { DtoCollectionWithRecord } from '../common/interfaces/dto_collection'; 7 | import { ProjectFiles } from '../common/interfaces/dto_project_data'; 8 | import { DtoCollectionWithMock } from '../common/interfaces/dto_mock_collection'; 9 | 10 | export interface UserData { 11 | 12 | user: User; 13 | 14 | collection: DtoCollectionWithRecord; 15 | 16 | projects: _.Dictionary; 17 | 18 | environments: _.Dictionary; 19 | 20 | schedules: _.Dictionary; 21 | 22 | schedulePageSize: number; 23 | 24 | stresses: _.Dictionary; 25 | 26 | mockCollections: DtoCollectionWithMock; 27 | 28 | projectFiles: ProjectFiles; 29 | 30 | defaultHeaders: string; 31 | 32 | syncInterval: number; 33 | 34 | sync: boolean; 35 | 36 | enableUpload: boolean; 37 | } -------------------------------------------------------------------------------- /api/src/interfaces/stress_case_info.ts: -------------------------------------------------------------------------------- 1 | import { StressMessageType } from '../common/enum/stress_type'; 2 | import { RecordEx } from '../models/record'; 3 | 4 | export interface StressUser { 5 | 6 | id: string; 7 | } 8 | 9 | export interface StressRequest extends StressUser { 10 | 11 | type: StressMessageType; 12 | 13 | stressId: string; 14 | 15 | stressName: string; 16 | 17 | testCase: TestCase; 18 | 19 | fileData: Buffer; 20 | } 21 | 22 | export interface TestCase { 23 | 24 | records: RecordEx[]; 25 | 26 | envId: string; 27 | 28 | requestBodyList?: RequestBody[]; 29 | 30 | envVariables: _.Dictionary; 31 | 32 | repeat: number; 33 | 34 | concurrencyCount: number; 35 | 36 | qps: number; 37 | 38 | timeout: number; 39 | 40 | keepAlive: boolean; 41 | } 42 | 43 | export interface RequestBody { 44 | 45 | id: string; 46 | 47 | name: string; 48 | 49 | param: string; 50 | 51 | method: string; 52 | 53 | url: string; 54 | 55 | body?: string; 56 | 57 | headers?: _.Dictionary; 58 | 59 | test?: string; 60 | 61 | prescript?: string; 62 | } -------------------------------------------------------------------------------- /cn/Simple_Tutorial/Create_Project.md: -------------------------------------------------------------------------------- 1 | #### 创建Project 2 | 3 | 首先,我们需要为API创建一个Project。 4 | 5 | 进入Project模块, 单击`create project` 按钮,然后会添加一个Project项,输入名字 `SampleAPI`: 6 | 7 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/simple_tutorial/create_project.png) 8 | 9 | #### 邀请成员 10 | 11 | 项目创建完成后,我们可以邀请team的成员加入到这个Project里,来协作开发维护这个Project的API: 12 | 13 | 1. 单击 `Invite Members` 按钮. 14 | 15 | 2. 以 `;` 为分隔符在弹出的对话框里输入成员的email. 16 | 17 | 3. 单击OK按钮完成邀请. 18 | 19 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/simple_tutorial/project_invite.png) 20 | 21 | 现在刚刚邀请的成员会收到一封邀请邮件,他们可以单击邮件里的`Accept`按钮来接受邀请并加入到这个项目中,接受邀请后会再次收到系统发送的用户名和密码的邮件。 22 | 23 | 成员加入后就可以在成员列表中看到这些成员了。 24 | 25 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/simple_tutorial/project_member.png) 26 | 27 | 只有Project的创建者可以移出成员,同样也只有创建者才能解散这个Project,解散后,项目下的所有成员都会失去这个项目的所有数据,包括Collection和Request。 28 | 29 | 注意到,在成员列表中有一列叫 `localhost`,所有成员都可以编辑它。这个参数的目的是用来在调试时让server知道localhost的指向,这样不是server的其他机器也能使用localhost来调试了。 30 | 31 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/simple_tutorial/project_localhost.png) -------------------------------------------------------------------------------- /client/src/components/highlight_code/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import hljs from 'highlight.js/lib/highlight'; 3 | import 'highlight.js/styles/tomorrow.css'; 4 | 5 | interface HighlightCodeProps { 6 | 7 | code: string; 8 | } 9 | 10 | interface HighlightCodeState { } 11 | 12 | class HighlightCode extends React.Component { 13 | 14 | pre; 15 | 16 | public componentDidMount() { 17 | hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript')); 18 | hljs.registerLanguage('json', require('highlight.js/lib/languages/json')); 19 | hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml')); 20 | hljs.highlightBlock(this.pre); 21 | } 22 | 23 | public componentDidUpdate(_prevProps: HighlightCodeProps, _prevState: HighlightCodeState) { 24 | hljs.highlightBlock(this.pre); 25 | } 26 | 27 | public render() { 28 | return ( 29 |
 this.pre = e} style={{ background: 'transparent' }}>{this.props.code}
30 | ); 31 | } 32 | } 33 | 34 | export default HighlightCode; -------------------------------------------------------------------------------- /en/Simple_Tutorial/Create_Collection.md: -------------------------------------------------------------------------------- 1 | #### Create collection 2 | 3 | Now we have a Project, we can create a Collection for this project. 4 | 5 | Collection is a group of many requests with the same property, 6 | it include folder, so the requests can be organized into folder. 7 | 8 | Collection is the unit when run Schedule or Stress Test. 9 | 10 | Create a Collection: 11 | 12 | 1. Go to Collection module. 13 | 14 | 2. Click `create collection` button as below. 15 | 16 | 3. Enter name `Sample Collection` and select Project `SampleAPI` that we create previously. 17 | 18 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/simple_tutorial/collection_create.png) 19 | 20 | After this collection is created, each of project's member can get it with their account. 21 | 22 | #### Collection other options 23 | 24 | 1. Create folder 25 | 26 | 2. Common Pre Request Script, refer to [Script](../Script/Common_Pre_Script.md) 27 | 28 | 3. Request Strict SSL: All requests of this collection will check SSL cert if this option is checked. 29 | 30 | 4. Request Follow Redirect: All request of this collection will follow http 3** redirect if this option is checked. -------------------------------------------------------------------------------- /api/src/services/web_socket_service.ts: -------------------------------------------------------------------------------- 1 | import * as WS from 'ws'; 2 | import * as http from 'http'; 3 | import { ScheduleOnDemandService } from './schedule_on_demand_service'; 4 | import { WebSocketHandler } from './base/web_socket_handler'; 5 | import { StressTestWSService } from './stress_test_ws_service'; 6 | 7 | export class WebSocketService { 8 | 9 | private wsServer: WS.Server; 10 | 11 | private routes: { [key: string]: { new(): WebSocketHandler } } = {}; 12 | 13 | constructor(server: http.Server) { 14 | this.wsServer = new WS.Server({ server }); 15 | this.use('/schedule', ScheduleOnDemandService); 16 | this.use('/stresstest', StressTestWSService); 17 | } 18 | 19 | use(path: string, handler: { new(): WebSocketHandler }) { 20 | this.routes[path] = handler; 21 | } 22 | 23 | start() { 24 | this.wsServer.on('connection', (socket, req) => { 25 | const route = this.routes[req.url]; 26 | if (!route) { 27 | socket.close(1000, `no handler for ${req.url}`); 28 | return; 29 | } 30 | new route().handle(socket); 31 | }); 32 | } 33 | } -------------------------------------------------------------------------------- /client/src/common/interfaces/postman_v1.ts: -------------------------------------------------------------------------------- 1 | import { DtoRecord } from './dto_record'; 2 | import { DtoEnvironment } from './dto_environment'; 3 | import { DtoVariable } from './dto_variable'; 4 | import { DataMode } from '../enum/data_mode'; 5 | 6 | export interface PostmanAllV1 { 7 | 8 | collections: PostmanCollectionV1[]; 9 | 10 | environments: PostmanEnvironments[]; 11 | } 12 | 13 | export interface PostmanCollectionV1 { 14 | 15 | id: string; 16 | 17 | name: string; 18 | 19 | description: string; 20 | 21 | commonPreScript: string; 22 | 23 | folders: PostmanRecord[]; 24 | 25 | requests: PostmanRecord[]; 26 | } 27 | 28 | export interface PostmanRecord extends DtoRecord { 29 | 30 | tests: string; 31 | 32 | folder: string; 33 | 34 | rawModeData: string; 35 | 36 | dataMode: DataMode & string; 37 | 38 | data: any; 39 | 40 | preRequestScript: string; 41 | 42 | order: string[]; 43 | } 44 | 45 | export interface PostmanEnvironments extends DtoEnvironment { 46 | 47 | values: PostmanEnv[]; 48 | } 49 | 50 | export interface PostmanEnv extends DtoVariable { 51 | 52 | enabled: boolean; 53 | 54 | type: string; 55 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/security-issue-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Security Issue report (提交安全性问题) 3 | about: File a security issue report. 4 | 5 | --- 6 | 7 | **1. Describe the Security Issue as detailed as possible `(问题描述)`** 8 | A clear and concise description of what the bug is (请详细描述问题) 9 | 10 | **2. Steps to reproduce the problem `(重现步骤)`:** 11 | Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner (简洁清晰的重现步骤能够帮助我们更迅速地定位问题所在) 12 | 13 | **3. What is the expected behavior `(期望表现)`?** 14 | Please describe the expected behavior (请描述期望表现) 15 | 16 | **4. Did this work before `(之前是否正常)`?** 17 | [Yes/No/NaN] [是,否,之前没用过] 18 | 19 | **5. Environment `(环境)`:** 20 | - Hitchhiker Version (Hitchhiker版本): [e.g. V0.14] 21 | - Use Docker or Installation Package (使用Docker还是安装包): [e.g. Docker/Installation Package] 22 | - Node Version (If you use the installation package) (如果使用安装包的Node版本): [e.g. 8.11.3] 23 | - Server OS (部署的系统): [e.g. Ubuntu, Windows, MacOS] 24 | - Browser (浏览器): [e.g. Chrome, Firefox, Safari] 25 | 26 | **6. Additional Information (e.g. Screenshots, Links) `(补充信息,截图,链接等)`** 27 | Add any other context about the problem here (补充具体的上下文信息) 28 | -------------------------------------------------------------------------------- /cn/Variable/Param_Var.md: -------------------------------------------------------------------------------- 1 | Parameters变量是在Request的Parameters tab下定义的。 2 | 它可以让你把多个Request合并成一个,减少维护成本。 3 | 4 | 例如, 有一个API: `http://httpbin.org/get?boy={{boy}}&girl={{girl}}`, 可以看到API里有2个参数。 5 | 现在在Parameters里面加上如下对象,表示每个参数各有3个不同的值: 6 | 7 | ```json 8 | { 9 | "boy": ["tom", "jerry", "mike"], 10 | "girl": ["lucy", "lily", "cristina"] 11 | } 12 | ``` 13 | 14 | 结果,选`OneToOne`的话我们可以得到3个Request: 15 | ``` 16 | http://httpbin.org/get?boy=tom&girl=lucy 17 | http://httpbin.org/get?boy=jerry&girl=lily 18 | http://httpbin.org/get?boy=mike&girl=cristina 19 | ``` 20 | 选`ManyToMany`的话就会有9个Request: 21 | ``` 22 | http://httpbin.org/get?boy=tom&girl=lucy 23 | http://httpbin.org/get?boy=tom&girl=lily 24 | http://httpbin.org/get?boy=tom&girl=cristina 25 | http://httpbin.org/get?boy=jerry&girl=lucy 26 | http://httpbin.org/get?boy=jerry&girl=lily 27 | http://httpbin.org/get?boy=jerry&girl=cristina 28 | http://httpbin.org/get?boy=mike&girl=lucy 29 | http://httpbin.org/get?boy=mike&girl=lily 30 | http://httpbin.org/get?boy=mike&girl=cristina 31 | ``` 32 | 33 | 而所有的这个Request只存在一个Request里。 34 | 35 | 我们可以同时请求这些不同参数的Request,也可以选择其中一个来请求。 36 | 37 | ![](https://raw.githubusercontent.com/brookshi/images/master/Hitchhiker/parameters.gif) -------------------------------------------------------------------------------- /client/src/modules/login/style/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | 3 | .login-page{ 4 | background: @panel-background-deep-dark; 5 | height: 100%; 6 | font-family: "Segoe UI", Arial, "Microsoft YaHei", -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Helvetica Neue", Helvetica, "Helvetica Neue For Number", sans-serif; 7 | } 8 | 9 | .login-page-desc-title{ 10 | font-size: 60px; 11 | color: white; 12 | margin-bottom: 24px; 13 | } 14 | 15 | .login-page-desc-content{ 16 | font-size: 26px; 17 | color: rgba(255,255,255,0.6); 18 | } 19 | 20 | .login-page .login-page-form { 21 | width: 450px; 22 | background-color: white; 23 | padding: 48px 36px; 24 | border-radius: 6px; 25 | div{ 26 | font-size: 15px; 27 | } 28 | } 29 | 30 | .login-panel-form-forgot { 31 | float: right; 32 | } 33 | 34 | .login-page-form-button { 35 | width: 100%; 36 | height: 44px; 37 | font-size: 18px; 38 | margin: 16px 0px 12px 0px; 39 | } 40 | 41 | .login-page-form-input{ 42 | height: 40px; 43 | } 44 | 45 | .login-page-loading{ 46 | position: absolute; 47 | top: calc(40% - 50px); 48 | left: calc(50% - 50px); 49 | } -------------------------------------------------------------------------------- /cn/Stress/README.md: -------------------------------------------------------------------------------- 1 | Hitchhiker 现在有两种压力测试方式,一个基于Go的 [Hitchhiker-Node](https://github.com/brookshi/Hitchhiker-Node), 另一个是基于Nodejs的,内置在Server里。 2 | 3 | #### 为什么会有两种: 4 | 5 | 最开始Hitchhiker的脚本功能并不复杂,不支持js库,async/await,以及文件读取保存等,而Go很适合做这种高并发的程序,做了下调查后,使用otto做为js解释器,是可以运行当前的脚本逻辑的,所以选用了Go做压力测试的节点,早期是够用的。 6 | 7 | 后来Hitchhiker开始支持更多复杂的脚本功能,比如自定义js库这一项,因为npm里的很多js库都调用了Node的库,目前的Go以及otto满足不了这种需求,除非再加一个Node进程来执行脚本,这样过于复杂且不如直接使用Node来写,所以综合考虑后还是使用Nodejs重写了压力测试点。 8 | 9 | #### 两种方法的优劣: 10 | 11 | Go的高并发以及goroutine让写起这种压力程序时非常之轻松,性能也很有保障,缺点还是在于Hitchhiker的脚本是js,所以Go执行起来比较费劲,也因此Go的程序不支持Hitchiker脚本的高级特性。 12 | 13 | Nodejs写这种压力测试程序就比较费劲,需要自己管理多进程,以及进程间通信,好在Hitchhiker Server也是基于Nodejs的,所以可以重用请求处理的逻辑,而且Api的压力测试本质上是高IO的,所以Nodejs的性能也很不错。不过Nodejs的程序目前还不支持分布式,稍后会加上去,主体功能已经完成。 14 | 15 | 稍微比较了下两者的性能,在单机上基本旗鼓相当。 16 | 17 | #### 后续: 18 | 19 | 目前是以基于Nodejs的版本为默认的,Go的暂时会停止维护,除非Go有了基于Node的js解释器,那时再考虑移回来。 20 | 21 | #### 用法 22 | 23 | 和Schedule一样,Stress Test运行的单位也是Collection,同样可以对Request进行排序,然后设置压力相关的参数,如并发数,请求次数等来模拟用户的真实场景。 24 | 25 | 下面来一步步创建一个Stress Test: 26 | 27 | 1. [创建一个Stress Test](Create_Stress.md). 28 | 29 | 2. [运行一个压力点](Node.md). Go的需要这么做,Nodejs的就不需要了。类型选择参考:[configuration](../installation/configuration.md)里的Stress Type 30 | 31 | 3. [运行](Run.md). -------------------------------------------------------------------------------- /api/src/controllers/stress_controller.ts: -------------------------------------------------------------------------------- 1 | import { GET, POST, PUT, DELETE, BodyParam, PathParam, BaseController } from 'webapi-router'; 2 | import { ResObject } from '../interfaces/res_object'; 3 | import * as Koa from 'koa'; 4 | import { DtoStress } from '../common/interfaces/dto_stress'; 5 | import { StressService } from '../services/stress_service'; 6 | 7 | export default class StressController extends BaseController { 8 | 9 | @POST('/stress') 10 | async createNew(ctx: Koa.Context, @BodyParam stress: DtoStress): Promise { 11 | return StressService.createNew(stress, (ctx).session.user); 12 | } 13 | 14 | @PUT('/stress') 15 | async update(@BodyParam stress: DtoStress): Promise { 16 | return StressService.update(stress); 17 | } 18 | 19 | @DELETE('/stress/:id') 20 | async delete(@PathParam('id') id: string): Promise { 21 | return StressService.delete(id); 22 | } 23 | 24 | @GET('/stresses') 25 | async getStresses(ctx: Koa.Context): Promise { 26 | const stresses = await StressService.getByUserId((ctx).session.userId); 27 | return { success: true, message: '', result: stresses }; 28 | } 29 | } -------------------------------------------------------------------------------- /api/src/models/mock_collection.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryColumn, Column, CreateDateColumn, OneToOne, OneToMany, JoinColumn, ManyToOne, UpdateDateColumn } from 'typeorm'; 2 | import { User } from './user'; 3 | import { Project } from './project'; 4 | import { Mock } from './mock'; 5 | import { DtoHeader } from '../common/interfaces/dto_header'; 6 | 7 | @Entity() 8 | export class MockCollection { 9 | 10 | @PrimaryColumn() 11 | id: string; 12 | 13 | @Column() 14 | name: string; 15 | 16 | @OneToMany(_type => Mock, mock => mock.collection, { 17 | cascadeInsert: true 18 | }) 19 | mocks: Mock[]; 20 | 21 | @Column('text', { nullable: true }) 22 | description: string; 23 | 24 | @JoinColumn() 25 | @OneToOne(_type => User) 26 | owner: User; 27 | 28 | @ManyToOne(_type => Project, project => project.collections) 29 | project: Project; 30 | 31 | @Column({ default: false }) 32 | recycle: boolean; 33 | 34 | @Column({ default: true }) 35 | public: boolean; 36 | 37 | @Column('json', { nullable: true }) 38 | headers: DtoHeader[]; 39 | 40 | @CreateDateColumn() 41 | createDate: Date; 42 | 43 | @UpdateDateColumn() 44 | updateDate: Date; 45 | } -------------------------------------------------------------------------------- /client/src/modules/project/style/index.less: -------------------------------------------------------------------------------- 1 | @import '../../../style/colors.less'; 2 | 3 | .project-list{ 4 | .ant-menu-item{ 5 | height: 60px; 6 | line-height: 60px; 7 | border-bottom: 1px solid @border-color-dark; 8 | } 9 | 10 | .item-with-menu-title{ 11 | font-size: 16px; 12 | } 13 | } 14 | 15 | .project-table{ 16 | background: white; 17 | .ant-table-row{ 18 | line-height: 10px; 19 | } 20 | } 21 | 22 | .project-add-btn{ 23 | font-size: 20px; 24 | top: 2px; 25 | right: 4px; 26 | } 27 | 28 | .project-title{ 29 | height: 30px; 30 | margin-left: 4px; 31 | font-size: 16px; 32 | position: relative; 33 | } 34 | 35 | .project-right-panel{ 36 | margin-top: 4px; 37 | padding: 0px 8px; 38 | } 39 | 40 | .project-create-btn{ 41 | position: absolute; 42 | right: 4px; 43 | border: none; 44 | } 45 | 46 | 47 | .env-variable-title{ 48 | padding: 4px 0px 4px 4px; 49 | margin: 4px 0px; 50 | background: @panel-background; 51 | position: relative; 52 | } 53 | 54 | .env-variable-tip { 55 | color: @place-holder-color; 56 | margin-bottom: 8px; 57 | } 58 | 59 | .env-variable-mode-btn{ 60 | position: absolute; 61 | right: 0px; 62 | top: 0px; 63 | } -------------------------------------------------------------------------------- /client/src/reducer/document.ts: -------------------------------------------------------------------------------- 1 | import { DocumentState, documentDefaultValue } from '../state/document'; 2 | import { DocumentActiveRecordType, DocumentCollectionOpenKeysType, DocumentSelectedProjectChangedType, ScrollDocumentType, DocumentActiveEnvIdType } from '../action/document'; 3 | 4 | export function documentState(state: DocumentState = documentDefaultValue, action: any): DocumentState { 5 | switch (action.type) { 6 | case DocumentActiveRecordType: { 7 | return { ...state, documentActiveRecord: action.value.id, changeByScroll: false }; 8 | } 9 | case DocumentCollectionOpenKeysType: { 10 | return { ...state, documentCollectionOpenKeys: action.value }; 11 | } 12 | case DocumentSelectedProjectChangedType: { 13 | return { ...state, documentSelectedProject: action.value }; 14 | } 15 | case ScrollDocumentType: { 16 | return { ...state, scrollTop: action.value, changeByScroll: true }; 17 | } 18 | case DocumentActiveEnvIdType: { 19 | const { projectId, envId } = action.value; 20 | return { ...state, activeEnv: { ...state.activeEnv, [projectId]: envId } }; 21 | } 22 | default: 23 | return state; 24 | } 25 | } -------------------------------------------------------------------------------- /api/src/middlewares/middleware.ts: -------------------------------------------------------------------------------- 1 | import * as Compose from 'koa-compose'; 2 | import * as Bodyparser from 'koa-bodyparser'; 3 | import * as Session from 'koa-session-minimal'; 4 | import { WebApiRouter } from 'webapi-router'; 5 | import sessionHandle from './session_handle'; 6 | import { SessionService } from '../services/session_service'; 7 | import routeFailed from './route_failed'; 8 | import errorHandle from './error_handle'; 9 | import * as KoaStatic from 'koa-static'; 10 | import * as Path from 'path'; 11 | import asyncInit from './async_init'; 12 | import * as Compress from 'koa-compress'; 13 | 14 | export default function middleware() { 15 | const ctrlRouter = new WebApiRouter(); 16 | return Compose( 17 | [ 18 | asyncInit(), 19 | errorHandle(), 20 | KoaStatic(Path.join(__dirname, '../public'), { gzip: true, maxage: 60 * 60 * 24 * 30 * 12 }), 21 | Session({ 22 | cookie: { 23 | maxAge: SessionService.maxAge 24 | } 25 | }), 26 | sessionHandle(), 27 | Compress(), 28 | Bodyparser({ jsonLimit: '50mb' }), 29 | ctrlRouter.router('../build/controllers', 'api'), 30 | routeFailed(), 31 | ] 32 | ); 33 | } -------------------------------------------------------------------------------- /client/src/components/res_error_panel/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StringUtil } from '../../utils/string_util'; 3 | import './style/index.less'; 4 | import { Input } from 'antd'; 5 | import { DtoError } from '../../common/interfaces/dto_error'; 6 | import Msg from '../../locales'; 7 | 8 | const { TextArea } = Input; 9 | 10 | interface ResErrorPanelProps { 11 | url?: string; 12 | error?: DtoError; 13 | } 14 | 15 | interface ResErrorPanelState { } 16 | 17 | class ResErrorPanel extends React.Component { 18 | 19 | public render() { 20 | const { url, error } = this.props; 21 | const errorStr = StringUtil.beautify(JSON.stringify(error), 'json'); 22 | return ( 23 |
24 |
{Msg('Component.Response')}
25 |
{Msg('Component.GetNonResponse')}
26 |
{Msg('Component.GetErrorFromUrl', { url: {url} })}
27 |