├── web ├── .env ├── .env.production ├── src │ ├── vite-env.d.ts │ ├── assets │ │ └── logo.png │ ├── constants │ │ ├── style.ts │ │ └── env.ts │ ├── shims-vue.d.ts │ ├── views │ │ ├── Login.tsx │ │ ├── Register.tsx │ │ ├── configurations │ │ │ ├── BlockIPs.tsx │ │ │ ├── SignedKeys.tsx │ │ │ ├── Emails.tsx │ │ │ ├── MockTime.tsx │ │ │ ├── Configs.tsx │ │ │ ├── RequestConcurrencies.tsx │ │ │ ├── HTTPServerInterceptors.tsx │ │ │ └── SessionInterceptors.tsx │ │ └── detectors │ │ │ ├── Ping.tsx │ │ │ └── TCP.tsx │ ├── helpers │ │ └── http-error.ts │ ├── main.ts │ ├── main.css │ ├── states │ │ └── admin.ts │ ├── components │ │ ├── ExFormInterface.tsx │ │ ├── ExLoading.tsx │ │ ├── ExDetectorTable.tsx │ │ ├── ExFluxDetail.tsx │ │ └── ExUserSelect.tsx │ ├── routes │ │ ├── router.ts │ │ └── index.ts │ ├── storages │ │ └── local.ts │ └── Root.tsx ├── public │ └── favicon.ico ├── .eslintignore ├── .eslintrc.js ├── index.html ├── tsconfig.json ├── vite.config.ts ├── package.json └── README.md ├── .dockerignore ├── hooks └── pre-commit ├── asset ├── dist │ ├── assets │ │ ├── Detector-2eea4969.css │ │ ├── DNSResult-469dd845.css │ │ ├── ExConfigEditorList-d9dcbf54.css │ │ ├── HTTPResult-1372e26e.css │ │ ├── PingResult-bb2df28a.css │ │ ├── TCPResult-d1b9c3a1.css │ │ ├── DatabaseResult-91ba9c30.css │ │ ├── Users-987e0845.css │ │ ├── ExDetectorResultTable-7e6b265e.css │ │ ├── Requests-b25ed006.css │ │ ├── Database-054a89cc.css │ │ ├── ExLoading-8e54e9e5.css │ │ ├── ExTable-08f645c7.css │ │ ├── ExFluxDetail-5d574f48.css │ │ ├── ExLoginRegister-85c3f6b4.css │ │ ├── Login-2fc5b58e.js │ │ ├── Register-55c86676.js │ │ ├── ExFormInterface-d830d527.js │ │ ├── ExLoading-572e76eb.js │ │ ├── index-fc3ae4d7.css │ │ ├── BlockIPs-483ba5e4.js │ │ ├── SignedKeys-f01bc2d6.js │ │ ├── Emails-a0c0a272.js │ │ ├── MockTime-72101ce2.js │ │ ├── Ping-898e4802.js │ │ ├── TCP-b7c9b1e1.js │ │ ├── Configs-499aae23.js │ │ ├── ExConfigEditorList-10d35f0e.js │ │ ├── DNS-16d14de9.js │ │ ├── ExConfigTable-74c02379.js │ │ ├── RequestConcurrencies-160bdd88.js │ │ ├── PingResult-e88b1dd9.js │ │ ├── TCPResult-cc687576.js │ │ ├── DatabaseResult-963f24cb.js │ │ ├── Logins-188ecd5f.js │ │ ├── DNSResult-8c28b47b.js │ │ ├── HTTPServerInterceptors-873741cf.js │ │ ├── SessionInterceptors-01600b82.js │ │ ├── configs-5a9029c7.js │ │ ├── Caches-62ddc5f6.js │ │ ├── RouterMocks-02b867d7.js │ │ ├── HTTPResult-a7bd9ce2.js │ │ ├── HTTP-5b90fc3c.js │ │ ├── Profile-b96e6843.js │ │ ├── Home-0972bc05.js │ │ ├── Database-729075d0.js │ │ ├── HTTPErrors-446f87b9.js │ │ ├── ExDetectorResultTable-2191f62a.js │ │ └── ExLoginRegister-baddf736.js │ ├── favicon.ico │ └── index.html ├── http_server_interceptor.js └── asset.go ├── images ├── profile.jpg ├── dns-setting.jpg ├── tcp-setting.jpg ├── http-setting.jpg ├── main-setting.jpg ├── ping-setting.jpg ├── database-setting.jpg ├── dns-detect-result.jpg ├── http-detect-result.jpg ├── ping-detect-result.jpg ├── tcp-detect-result.jpg ├── database-detect-result.jpg ├── dns-detect-result-detail.jpg ├── http-detect-result-detail.jpg ├── ping-detect-result-detail.jpg ├── tcp-detect-result-detail.jpg └── database-detect-result-detail.jpg ├── entrypoint.sh ├── config ├── production.yml ├── test.yml ├── dev.yml └── default.yml ├── controller ├── controller_doc.go ├── user_doc.go ├── admin.go ├── asset.go └── asset_test.go ├── QUESTIONS.md ├── download-swagger.sh ├── ent ├── runtime │ └── runtime.go ├── marshal.go ├── predicate │ └── predicate.go └── enttest │ └── enttest.go ├── template ├── additional.tmpl └── marshal.tmpl ├── router_mock ├── router_mock_test.go └── router_mock.go ├── .golangci.yml ├── middleware ├── middleware.go ├── stats_test.go ├── ip_blocker.go ├── url_prefix.go ├── ip_blocker_test.go ├── entry_test.go ├── interceptor.go ├── error_test.go ├── router_mocker.go └── entry.go ├── schema ├── tcpdetector.go ├── pingdetector.go ├── dnsdetector.go ├── httpdetector.go ├── tcpdetectorresult.go ├── dnsdetectorresult.go ├── databasedetector.go ├── databasedetectorresult.go ├── time.go ├── pingdetectorresult.go ├── userlogin.go ├── httpdetectorresult.go └── status.go ├── location ├── urls.go ├── location_test.go └── location.go ├── validate ├── file.go ├── flux.go ├── configuration.go ├── detector.go ├── user.go └── common.go ├── .goreleaser.yml ├── service ├── service.go ├── ip_blocker_test.go ├── ip_blocker.go ├── performance_test.go ├── application_test.go ├── captcha_test.go └── application.go ├── .gitignore ├── util ├── stack_test.go ├── env_test.go ├── env.go ├── number.go ├── map_test.go ├── number_test.go ├── map.go ├── context_test.go ├── stack.go ├── string_test.go └── date_test.go ├── .github └── workflows │ └── build.yml ├── helper ├── snappy.go ├── redis_test.go ├── ent_test.go ├── influxdb_test.go └── snappy_test.go ├── Dockerfile ├── router_concurrency └── router_concurrency_test.go ├── cs ├── cs.go └── action.go ├── detector ├── detector_test.go └── database_test.go ├── router └── router.go ├── Makefile ├── profiler └── profiler.go ├── session ├── session_test.go └── session.go ├── log └── log_test.go ├── .air.toml ├── interceptor └── session.go └── request └── http_instance.go /web/.env: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/.env.production: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | web/node_modules -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | make lint && make lint-web -------------------------------------------------------------------------------- /web/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /asset/dist/assets/Detector-2eea4969.css: -------------------------------------------------------------------------------- 1 | .a1r7hurf{width:100%;margin-top:20px} 2 | -------------------------------------------------------------------------------- /asset/dist/assets/DNSResult-469dd845.css: -------------------------------------------------------------------------------- 1 | .p1iljsez{max-width:800px;white-space:nowrap} 2 | -------------------------------------------------------------------------------- /asset/dist/assets/ExConfigEditorList-d9dcbf54.css: -------------------------------------------------------------------------------- 1 | .apayaz1{width:100%;margin-top:20px} 2 | -------------------------------------------------------------------------------- /asset/dist/assets/HTTPResult-1372e26e.css: -------------------------------------------------------------------------------- 1 | .p1777rsr{max-width:980px;white-space:nowrap} 2 | -------------------------------------------------------------------------------- /asset/dist/assets/PingResult-bb2df28a.css: -------------------------------------------------------------------------------- 1 | .p15nhdzn{max-width:800px;white-space:nowrap} 2 | -------------------------------------------------------------------------------- /asset/dist/assets/TCPResult-d1b9c3a1.css: -------------------------------------------------------------------------------- 1 | .p1m3677v{max-width:800px;white-space:nowrap} 2 | -------------------------------------------------------------------------------- /images/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/profile.jpg -------------------------------------------------------------------------------- /asset/dist/assets/DatabaseResult-91ba9c30.css: -------------------------------------------------------------------------------- 1 | .pux6hxu{max-width:800px;white-space:nowrap} 2 | -------------------------------------------------------------------------------- /asset/dist/assets/Users-987e0845.css: -------------------------------------------------------------------------------- 1 | .ulmcati{margin:0;padding:0;list-style-position:insied} 2 | -------------------------------------------------------------------------------- /asset/dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/asset/dist/favicon.ico -------------------------------------------------------------------------------- /images/dns-setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/dns-setting.jpg -------------------------------------------------------------------------------- /images/tcp-setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/tcp-setting.jpg -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/web/public/favicon.ico -------------------------------------------------------------------------------- /asset/dist/assets/ExDetectorResultTable-7e6b265e.css: -------------------------------------------------------------------------------- 1 | .s97967t{padding:10px 15px;cursor:pointer} 2 | -------------------------------------------------------------------------------- /asset/dist/assets/Requests-b25ed006.css: -------------------------------------------------------------------------------- 1 | .u1nflspg{margin:0;padding:0;list-style-position:inside} 2 | -------------------------------------------------------------------------------- /images/http-setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/http-setting.jpg -------------------------------------------------------------------------------- /images/main-setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/main-setting.jpg -------------------------------------------------------------------------------- /images/ping-setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/ping-setting.jpg -------------------------------------------------------------------------------- /web/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/web/src/assets/logo.png -------------------------------------------------------------------------------- /asset/dist/assets/Database-054a89cc.css: -------------------------------------------------------------------------------- 1 | .cg46r4v{margin:0;padding:0;list-style:inside;line-height:2.5em} 2 | -------------------------------------------------------------------------------- /images/database-setting.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/database-setting.jpg -------------------------------------------------------------------------------- /images/dns-detect-result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/dns-detect-result.jpg -------------------------------------------------------------------------------- /images/http-detect-result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/http-detect-result.jpg -------------------------------------------------------------------------------- /images/ping-detect-result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/ping-detect-result.jpg -------------------------------------------------------------------------------- /images/tcp-detect-result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/tcp-detect-result.jpg -------------------------------------------------------------------------------- /asset/dist/assets/ExLoading-8e54e9e5.css: -------------------------------------------------------------------------------- 1 | .s5jcqi3{float:left;margin-right:10px}.l53tqur{margin:auto;width:200px} 2 | -------------------------------------------------------------------------------- /images/database-detect-result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/database-detect-result.jpg -------------------------------------------------------------------------------- /asset/dist/assets/ExTable-08f645c7.css: -------------------------------------------------------------------------------- 1 | .pyx5alf{margin-top:10px;float:right}.u13tuy86{padding:0;margin:0;list-style:inside} 2 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [ "${1:0:1}" = '-' ]; then 5 | set -- cybertect "$@" 6 | fi 7 | 8 | exec "$@" -------------------------------------------------------------------------------- /images/dns-detect-result-detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/dns-detect-result-detail.jpg -------------------------------------------------------------------------------- /images/http-detect-result-detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/http-detect-result-detail.jpg -------------------------------------------------------------------------------- /images/ping-detect-result-detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/ping-detect-result-detail.jpg -------------------------------------------------------------------------------- /images/tcp-detect-result-detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/tcp-detect-result-detail.jpg -------------------------------------------------------------------------------- /config/production.yml: -------------------------------------------------------------------------------- 1 | # 仅需要添加与default不相同的配置 2 | # 生产环境配置 3 | 4 | redis: 5 | uri: redis://miniredis/?slow=200ms&maxProcessing=1000 -------------------------------------------------------------------------------- /images/database-detect-result-detail.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vicanso/cyber-tect/HEAD/images/database-detect-result-detail.jpg -------------------------------------------------------------------------------- /web/src/constants/style.ts: -------------------------------------------------------------------------------- 1 | export const mainHeaderHeight = 60; 2 | export const padding = 10; 3 | export const mainNavigationWidth = 200; 4 | -------------------------------------------------------------------------------- /asset/dist/assets/ExFluxDetail-5d574f48.css: -------------------------------------------------------------------------------- 1 | .i18la4si{margin:0;padding:0 20px;max-width:400px;word-wrap:break-word;word-break:break-all;white-space:normal;list-style-position:insied} 2 | -------------------------------------------------------------------------------- /asset/dist/assets/ExLoginRegister-85c3f6b4.css: -------------------------------------------------------------------------------- 1 | .c1eq9ar{max-width:640px;margin:120px auto}.c1ppv7dc{text-align:center;height:40px;cursor:pointer}.sd1d3ho{width:100%}.cvblerb{height:100%} 2 | -------------------------------------------------------------------------------- /config/test.yml: -------------------------------------------------------------------------------- 1 | # 仅需要添加与default不相同的配置 2 | # 测试环境配置 3 | redis: 4 | uri: redis://miniredis/?slow=200ms&maxProcessing=1000 5 | 6 | database: 7 | uri: postgres://postgres:postgres@127.0.0.1:5432/cybertect 8 | -------------------------------------------------------------------------------- /controller/controller_doc.go: -------------------------------------------------------------------------------- 1 | // +build swagger 2 | 3 | package controller 4 | 5 | // 204无响应数据 6 | // swagger:response apiNoContentResponse 7 | type apiNoContentResponse struct { 8 | Body string 9 | } 10 | -------------------------------------------------------------------------------- /web/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import { DefineComponent } from "vue"; 3 | // eslint-disable-next-line 4 | const component: DefineComponent<{}, {}, any>; 5 | export default component; 6 | } 7 | -------------------------------------------------------------------------------- /web/.eslintignore: -------------------------------------------------------------------------------- 1 | # don't ever lint node_modules 2 | node_modules 3 | # don't lint build output (make sure it's set to your correct build folder name) 4 | dist 5 | # don't lint nyc coverage output 6 | coverage 7 | 8 | .eslintrc.js 9 | -------------------------------------------------------------------------------- /QUESTIONS.md: -------------------------------------------------------------------------------- 1 | # 问题汇总 2 | 3 | ## Mini Redis 4 | 5 | 用户session等信息会使用redis保存,由于项目主要用于应用监控,登录用户数较少,为了减少对其它服务的依赖,因此使用[miniredis](https://github.com/alicebob/miniredis)替换真正的redis服务,miniredis是基于内存的,因此如果程序重启则sesion信息会清除。 6 | 7 | ## 超级用户 8 | 9 | 第一个注册的用户自动创建为超级用户。 -------------------------------------------------------------------------------- /config/dev.yml: -------------------------------------------------------------------------------- 1 | # 仅需要添加与default不相同的配置 2 | # 本地开发环境 3 | redis: 4 | uri: redis://127.0.0.1:6379/?slow=200ms&maxProcessing=1000 5 | 6 | database: 7 | uri: postgres://vicanso:A123456@127.0.0.1:5432/cybertect?maxIdleConns=5&maxIdleTime=30m&maxOpenConns=100 -------------------------------------------------------------------------------- /web/src/constants/env.ts: -------------------------------------------------------------------------------- 1 | const env = import.meta.env; 2 | 3 | // isDevelopment 是否开发环境 4 | export function isDevelopment(): boolean { 5 | return env.DEV; 6 | } 7 | 8 | // isProduction 是否生产环境 9 | export function isProduction(): boolean { 10 | return env.PROD; 11 | } 12 | -------------------------------------------------------------------------------- /web/src/views/Login.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | 3 | import ExLoginRegister from "../components/ExLoginRegister"; 4 | 5 | export default defineComponent({ 6 | name: "LoginView", 7 | render() { 8 | return ; 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /asset/dist/assets/Login-2fc5b58e.js: -------------------------------------------------------------------------------- 1 | import{E as e}from"./ExLoginRegister-baddf736.js";import{d as o,N as r}from"./ui-32540f7e.js";import"./index-aeeb7070.js";import"./common-a7d1cc73.js";import"./naive-094f875f.js";const p=o({name:"LoginView",render(){return r(e,null,null)}});export{p as default}; 2 | -------------------------------------------------------------------------------- /web/src/views/Register.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | import ExLoginRegister from "../components/ExLoginRegister"; 3 | 4 | export default defineComponent({ 5 | name: "RegisterView", 6 | render() { 7 | return ; 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /asset/dist/assets/Register-55c86676.js: -------------------------------------------------------------------------------- 1 | import{E as e}from"./ExLoginRegister-baddf736.js";import{d as r,N as t}from"./ui-32540f7e.js";import"./index-aeeb7070.js";import"./common-a7d1cc73.js";import"./naive-094f875f.js";const p=r({name:"RegisterView",render(){return t(e,{type:"register"},null)}});export{p as default}; 2 | -------------------------------------------------------------------------------- /download-swagger.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | download_url=$(curl -s https://api.github.com/repos/go-swagger/go-swagger/releases/latest | \ 4 | jq -r '.assets[] | select(.name | contains("'"$(uname | tr '[:upper:]' '[:lower:]')"'_amd64")) | .browser_download_url') 5 | curl -o /usr/local/bin/swagger -L'#' "$download_url" 6 | chmod +x /usr/local/bin/swagger 7 | -------------------------------------------------------------------------------- /asset/dist/assets/ExFormInterface-d830d527.js: -------------------------------------------------------------------------------- 1 | let u=function(t){return t.Select="select",t.MultiSelect="multiSelect",t.DateTime="dateTime",t.DateRange="dateRange",t.InputNumber="inputNumber",t.InputNumberGroup="inputGroup",t.InputDuration="inputDuration",t.DynamicInput="dynamicInput",t.TextArea="textArea",t.Blank="blank",t.MultiUserSelect="multiUserSelect",t}({});export{u as F}; 2 | -------------------------------------------------------------------------------- /web/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'vue-eslint-parser', 4 | plugins: [ 5 | '@typescript-eslint', 6 | ], 7 | parserOptions: { 8 | parser: '@typescript-eslint/parser', 9 | }, 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:@typescript-eslint/recommended', 13 | 'plugin:vue/vue3-recommended', 14 | ] 15 | }; -------------------------------------------------------------------------------- /asset/dist/assets/ExLoading-572e76eb.js: -------------------------------------------------------------------------------- 1 | import{d as t,N as s,c as o}from"./ui-32540f7e.js";import{s as n}from"./naive-094f875f.js";const a="s5jcqi3",r="l53tqur",u=t({name:"ExLoading",props:{style:{type:Object,default:()=>({marginTop:"60px"})}},render(){const{style:e}=this.$props;return s("div",{style:e,class:r},[s(n,{size:"small",class:a},null),o("正在加载中,请稍候...")])}});export{u as E}; 2 | -------------------------------------------------------------------------------- /ent/runtime/runtime.go: -------------------------------------------------------------------------------- 1 | // Code generated by ent, DO NOT EDIT. 2 | 3 | package runtime 4 | 5 | // The schema-stitching logic is generated in github.com/vicanso/cybertect/ent/runtime.go 6 | 7 | const ( 8 | Version = "v0.12.3" // Version of ent codegen. 9 | Sum = "h1:N5lO2EOrHpCH5HYfiMOCHYbo+oh5M8GjT0/cx5x6xkk=" // Sum of ent codegen. 10 | ) 11 | -------------------------------------------------------------------------------- /template/additional.tmpl: -------------------------------------------------------------------------------- 1 | {{ define "model/fields/additional" }} 2 | {{/* 添加额外字段 */}} 3 | {{- range $i, $f := $.Fields }} 4 | {{- if eq $f.Name "status" }} 5 | // 状态描述 6 | StatusDesc string `json:"statusDesc,omitempty"` 7 | {{- end }} 8 | 9 | {{- if eq $f.Name "task" }} 10 | // 状态描述 11 | TaskName string `json:"taskName,omitempty"` 12 | {{- end }} 13 | 14 | {{- end }} 15 | {{ end }} -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | cybertect 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "sourceMap": true, 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true, 11 | "lib": ["esnext", "dom"] 12 | }, 13 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 14 | } 15 | -------------------------------------------------------------------------------- /router_mock/router_mock_test.go: -------------------------------------------------------------------------------- 1 | package routermock 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRouterConfig(t *testing.T) { 10 | assert := assert.New(t) 11 | Update([]string{ 12 | `{ 13 | "route": "/", 14 | "method": "GET", 15 | "status": 400 16 | }`, 17 | }) 18 | routeConfig := Get("GET", "/") 19 | assert.Equal(400, routeConfig.Status) 20 | } 21 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | build-tags: 4 | - codeanalysis 5 | linters: 6 | exclusions: 7 | generated: lax 8 | presets: 9 | - comments 10 | - common-false-positives 11 | - legacy 12 | - std-error-handling 13 | paths: 14 | - third_party$ 15 | - builtin$ 16 | - examples$ 17 | - web$ 18 | formatters: 19 | exclusions: 20 | generated: lax 21 | paths: 22 | - third_party$ 23 | - builtin$ 24 | - examples$ 25 | -------------------------------------------------------------------------------- /web/src/helpers/http-error.ts: -------------------------------------------------------------------------------- 1 | class HTTPError extends Error { 2 | // http状态码 3 | status: number; 4 | // 是否异常 5 | exception?: boolean; 6 | // 出错信息 7 | message: string; 8 | // 出错分类 9 | category?: string; 10 | // 出错代码,如果某个出错需要单独处理,可定义唯一的出错码 11 | code?: string; 12 | // 子错误 13 | errs?: HTTPError[]; 14 | // 其它一些额外的信息 15 | extra?: Record; 16 | constructor(status: number, message: string) { 17 | super(message); 18 | this.status = status; 19 | this.message = message; 20 | } 21 | } 22 | 23 | export default HTTPError; 24 | -------------------------------------------------------------------------------- /web/src/main.ts: -------------------------------------------------------------------------------- 1 | import { create } from "naive-ui"; 2 | import { createApp } from "vue"; 3 | import Root from "./Root"; 4 | import router from "./routes/router"; 5 | import { settingStorage } from "./storages/local"; 6 | 7 | const naive = create(); 8 | const app = createApp(Root); 9 | app.use(router).use(naive); 10 | 11 | const loadSetting = async () => { 12 | try { 13 | await settingStorage.load(); 14 | } catch (err) { 15 | console.error(err); 16 | } 17 | }; 18 | 19 | router 20 | .isReady() 21 | .then(loadSetting) 22 | .then(() => app.mount("#app")) 23 | .catch(console.error); 24 | -------------------------------------------------------------------------------- /asset/dist/assets/index-fc3ae4d7.css: -------------------------------------------------------------------------------- 1 | .uuuzaf2{margin-right:5px}.h1xvxe3a{height:60px;line-height:60px;padding:0 30px}.lkvn6gz{float:left;cursor:pointer}.l18fhfgm{margin:50px auto;text-align:center}body{margin:0;font-size:14px;font-family:v-sans,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol;line-height:1.6;-webkit-text-size-adjust:100%}.clearfix:after{visibility:hidden;display:block;font-size:0;content:" ";clear:both;height:0}.widthFull{width:100%!important}.tac{text-align:center}.mright5{margin-right:5px}.mbottom15{margin-bottom:15px}.luwg8pj{top:60px!important}.c1llctjr{padding:20px} 2 | -------------------------------------------------------------------------------- /template/marshal.tmpl: -------------------------------------------------------------------------------- 1 | {{/* gotype: entgo.io/ent/entc/gen.Graph */}} 2 | 3 | {{ define "marshal" }} 4 | 5 | {{ $pkg := base $.Config.Package }} 6 | {{ template "header" $ }} 7 | 8 | import "encoding/json" 9 | 10 | {{ range $n := $.Nodes }} 11 | 12 | {{- range $i, $f := $n.Fields }} 13 | {{/* 如果有status字段,则调整MarshalJSON */}} 14 | {{- if eq $f.Name "status" }} 15 | type Marshal{{ $n.Name }} {{ $n.Name }} 16 | func (t *{{ $n.Name }}) MarshalJSON() ([]byte, error) { 17 | tmp := (*Marshal{{ $n.Name }})(t) 18 | tmp.StatusDesc = tmp.Status.String() 19 | return json.Marshal(tmp) 20 | } 21 | {{- end }} 22 | 23 | {{- end }} 24 | 25 | {{ end }} 26 | 27 | {{ end }} 28 | -------------------------------------------------------------------------------- /web/src/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-size: 14px; 4 | font-family: v-sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", 5 | sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 6 | line-height: 1.6; 7 | -webkit-text-size-adjust: 100%; 8 | } 9 | 10 | .clearfix:after { 11 | visibility: hidden; 12 | display: block; 13 | font-size: 0; 14 | content: " "; 15 | clear: both; 16 | height: 0; 17 | } 18 | 19 | .widthFull { 20 | width: 100% !important; 21 | } 22 | 23 | .tac { 24 | text-align: center; 25 | } 26 | 27 | .mright5 { 28 | margin-right: 5px; 29 | } 30 | .mbottom15 { 31 | margin-bottom: 15px; 32 | } -------------------------------------------------------------------------------- /web/src/states/admin.ts: -------------------------------------------------------------------------------- 1 | import request from "../helpers/request"; 2 | 3 | import { ADMINS_CACHE_ID } from "../constants/url"; 4 | 5 | interface CacheData { 6 | data: string; 7 | } 8 | 9 | // adminFindCacheByKey 查询缓存 10 | export async function adminFindCacheByKey(key: string): Promise { 11 | const url = ADMINS_CACHE_ID.replace(":key", key); 12 | const { data } = await request.get(url); 13 | return data; 14 | } 15 | 16 | // adminCleanCacheByKey 清除缓存 17 | export async function adminCleanCacheByKey(key: string): Promise { 18 | const url = ADMINS_CACHE_ID.replace(":key", key); 19 | await request.delete(url); 20 | return; 21 | } 22 | -------------------------------------------------------------------------------- /middleware/middleware.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | -------------------------------------------------------------------------------- /schema/tcpdetector.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | // TCPDetector holds the schema definition for the TCPDetector entity. 9 | type TCPDetector struct { 10 | ent.Schema 11 | } 12 | 13 | // Fields of the TCPDetector. 14 | func (TCPDetector) Fields() []ent.Field { 15 | return []ent.Field{ 16 | field.Strings("addrs"). 17 | Comment("检测地址列表"), 18 | } 19 | } 20 | 21 | // Mixin of the TCPDetector 22 | func (TCPDetector) Mixin() []ent.Mixin { 23 | return []ent.Mixin{ 24 | TimeMixin{}, 25 | StatusMixin{}, 26 | DetectorMixin{}, 27 | } 28 | } 29 | 30 | // Edges of the TCPDetector. 31 | func (TCPDetector) Edges() []ent.Edge { 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /asset/dist/assets/BlockIPs-483ba5e4.js: -------------------------------------------------------------------------------- 1 | import{E as t}from"./ExConfigEditorList-10d35f0e.js";import{C as r}from"./configs-5a9029c7.js";import{d as e,N as i}from"./ui-32540f7e.js";import"./ExConfigEditor-da704c67.js";import"./index-aeeb7070.js";import"./common-a7d1cc73.js";import"./naive-094f875f.js";import"./ExForm-cdb0faae.js";import"./detector-090ed643.js";import"./ExFormInterface-d830d527.js";import"./ExLoading-572e76eb.js";import"./ExConfigTable-74c02379.js";import"./ExTable-dcbd254b.js";const k=e({name:"BlockIPConfigs",render(){const o=[{name:"IP地址:",key:"data",placeholder:"请输入IP地址或网段"}];return i(t,{listTitle:"黑名单IP配置",editorTitle:"添加/更新黑名单配置",editorDescription:"用于拦截访问IP",category:r.BlockIP,extraFormItems:o},null)}});export{k as default}; 2 | -------------------------------------------------------------------------------- /schema/pingdetector.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | // PingDetector holds the schema definition for the PingDetector entity. 9 | type PingDetector struct { 10 | ent.Schema 11 | } 12 | 13 | // Fields of the PingDetector. 14 | func (PingDetector) Fields() []ent.Field { 15 | return []ent.Field{ 16 | field.Strings("ips"). 17 | Comment("检测IP列表"), 18 | } 19 | } 20 | 21 | // Mixin of the TCPDetector 22 | func (PingDetector) Mixin() []ent.Mixin { 23 | return []ent.Mixin{ 24 | TimeMixin{}, 25 | StatusMixin{}, 26 | DetectorMixin{}, 27 | } 28 | } 29 | 30 | // Edges of the PingDetector. 31 | func (PingDetector) Edges() []ent.Edge { 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /asset/dist/assets/SignedKeys-f01bc2d6.js: -------------------------------------------------------------------------------- 1 | import{E as e}from"./ExConfigEditorList-10d35f0e.js";import{C as o}from"./configs-5a9029c7.js";import{d as r,N as i}from"./ui-32540f7e.js";import"./ExConfigEditor-da704c67.js";import"./index-aeeb7070.js";import"./common-a7d1cc73.js";import"./naive-094f875f.js";import"./ExForm-cdb0faae.js";import"./detector-090ed643.js";import"./ExFormInterface-d830d527.js";import"./ExLoading-572e76eb.js";import"./ExConfigTable-74c02379.js";import"./ExTable-dcbd254b.js";const u=r({name:"SignedKeys",render(){const t=[{name:"密钥:",key:"data",placeholder:"请输入签名使用的密钥,多个密钥以,分隔"}];return i(e,{listTitle:"密钥配置",editorTitle:"添加/更新密钥配置",editorDescription:"配置用于生成session加密的密钥",category:o.SignedKey,extraFormItems:t},null)}});export{u as default}; 2 | -------------------------------------------------------------------------------- /location/urls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package location 16 | 17 | // 相关的URL 18 | const ( 19 | locationURL = "/ip-locations/json/:ip" 20 | ) 21 | -------------------------------------------------------------------------------- /web/src/views/configurations/BlockIPs.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | import ExConfigEditorList from "../../components/ExConfigEditorList"; 3 | import { ConfigCategory } from "../../states/configs"; 4 | 5 | export default defineComponent({ 6 | name: "BlockIPConfigs", 7 | render() { 8 | const extraFormItems = [ 9 | { 10 | name: "IP地址:", 11 | key: "data", 12 | placeholder: "请输入IP地址或网段", 13 | }, 14 | ]; 15 | return ( 16 | 23 | ); 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /web/src/views/configurations/SignedKeys.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | import ExConfigEditorList from "../../components/ExConfigEditorList"; 3 | import { ConfigCategory } from "../../states/configs"; 4 | 5 | export default defineComponent({ 6 | name: "SignedKeys", 7 | render() { 8 | const extraFormItems = [ 9 | { 10 | name: "密钥:", 11 | key: "data", 12 | placeholder: "请输入签名使用的密钥,多个密钥以,分隔", 13 | }, 14 | ]; 15 | return ( 16 | 23 | ); 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /asset/dist/assets/Emails-a0c0a272.js: -------------------------------------------------------------------------------- 1 | import{E as o}from"./ExConfigEditorList-10d35f0e.js";import{C as r}from"./configs-5a9029c7.js";import{F as e}from"./ExFormInterface-d830d527.js";import{d as i,N as m}from"./ui-32540f7e.js";import"./ExConfigEditor-da704c67.js";import"./index-aeeb7070.js";import"./common-a7d1cc73.js";import"./naive-094f875f.js";import"./ExForm-cdb0faae.js";import"./detector-090ed643.js";import"./ExLoading-572e76eb.js";import"./ExConfigTable-74c02379.js";import"./ExTable-dcbd254b.js";const F=i({name:"EmailConfigs",render(){const t=[{name:"邮箱地址:",key:"data",type:e.TextArea,span:24,placeholder:"请输入邮箱地址,多个地址以,分隔"}];return m(o,{listTitle:"邮箱地址配置",editorTitle:"添加/更新邮箱地址配置",editorDescription:"配置各类邮件接收列表",category:r.Email,extraFormItems:t},null)}});export{F as default}; 2 | -------------------------------------------------------------------------------- /asset/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | cybertect 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /validate/file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validate 16 | 17 | func init() { 18 | buckets := []string{ 19 | "files", 20 | } 21 | Add("xFileBucket", newIsInString(buckets)) 22 | } 23 | -------------------------------------------------------------------------------- /web/src/components/ExFormInterface.tsx: -------------------------------------------------------------------------------- 1 | export interface FormItem { 2 | name: string; 3 | key: string; 4 | type?: string; 5 | placeholder?: string; 6 | span?: number; 7 | defaultValue?: unknown; 8 | disabled?: boolean; 9 | // TODO 确认是否有其它方式表示 10 | // eslint-disable-next-line 11 | options?: any[]; 12 | suffix?: string; 13 | min?: number; 14 | max?: number; 15 | } 16 | 17 | export enum FormItemTypes { 18 | Select = "select", 19 | MultiSelect = "multiSelect", 20 | DateTime = "dateTime", 21 | DateRange = "dateRange", 22 | InputNumber = "inputNumber", 23 | InputNumberGroup = "inputGroup", 24 | InputDuration = "inputDuration", 25 | DynamicInput = "dynamicInput", 26 | TextArea = "textArea", 27 | Blank = "blank", 28 | MultiUserSelect = "multiUserSelect", 29 | } 30 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod download 7 | builds: 8 | - env: 9 | - CGO_ENABLED=0 10 | goos: 11 | - linux 12 | - windows 13 | - darwin 14 | goarch: 15 | - amd64 16 | - arm64 17 | archives: 18 | - replacements: 19 | darwin: Darwin 20 | linux: Linux 21 | windows: Windows 22 | 386: i386 23 | amd64: x86_64 24 | checksum: 25 | name_template: 'checksums.txt' 26 | snapshot: 27 | name_template: "{{ .Tag }}-next" 28 | changelog: 29 | sort: asc 30 | filters: 31 | exclude: 32 | - '^docs:' 33 | - '^test:' -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "github.com/vicanso/cybertect/cache" 19 | ) 20 | 21 | var ( 22 | redisSrv = cache.GetRedisCache() 23 | ) 24 | -------------------------------------------------------------------------------- /web/src/components/ExLoading.tsx: -------------------------------------------------------------------------------- 1 | import { NSpin } from "naive-ui"; 2 | import { defineComponent } from "vue"; 3 | import { css } from "@linaria/core"; 4 | 5 | const spinClass = css` 6 | float: left; 7 | margin-right: 10px; 8 | `; 9 | const loadingClass = css` 10 | margin: auto; 11 | width: 200px; 12 | `; 13 | 14 | export default defineComponent({ 15 | name: "ExLoading", 16 | props: { 17 | style: { 18 | type: Object, 19 | default: () => { 20 | return { 21 | marginTop: "60px", 22 | }; 23 | }, 24 | }, 25 | }, 26 | render() { 27 | const { style } = this.$props; 28 | return ( 29 |
30 | 31 | 正在加载中,请稍候... 32 |
33 | ); 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /asset/http_server_interceptor.js: -------------------------------------------------------------------------------- 1 | // 从请求的query中获取key对应的值 2 | function getReqQuery(key) { 3 | return req.query[key]; 4 | } 5 | 6 | // 修改请求头的query 7 | function setReqQuery(key, value) { 8 | req.modifiedQuery = true; 9 | req.query[key] = value; 10 | } 11 | 12 | // 从请求数据中获取body中key对应的值 13 | function getReqBody(key) { 14 | return req.body[key]; 15 | } 16 | 17 | // 设置请求数据中的body的值 18 | function setReqBody(key, value) { 19 | req.modifiedBody = true; 20 | req.body[key] = value; 21 | } 22 | 23 | // 设置响应数据 24 | function setRespBody(key, value) { 25 | if (!resp.status) { 26 | resp.status = 200; 27 | } 28 | resp.body[key] = value; 29 | } 30 | 31 | // 设置响应状态码 32 | function setRespStatus(status) { 33 | resp.status = status; 34 | } 35 | 36 | // 设置响应HTTP头 37 | function setRespHeader(key, value) { 38 | resp.header[key] = value; 39 | } -------------------------------------------------------------------------------- /validate/flux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validate 16 | 17 | func init() { 18 | // influxdb measurement 19 | AddAlias("xMeasurement", "ascii,min=1,max=20") 20 | // influxdb tag 21 | AddAlias("xTag", "ascii,min=1,max=200") 22 | } 23 | -------------------------------------------------------------------------------- /schema/dnsdetector.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | // DNSDetector holds the schema definition for the DNSDetector entity. 9 | type DNSDetector struct { 10 | ent.Schema 11 | } 12 | 13 | // Fields of the DNSDetector. 14 | func (DNSDetector) Fields() []ent.Field { 15 | return []ent.Field{ 16 | field.String("host"). 17 | NotEmpty(). 18 | Comment("域名地址"), 19 | field.Strings("ips"). 20 | Comment("域名配置的IP列表"), 21 | field.Strings("servers"). 22 | Comment("DNS服务器列表"), 23 | } 24 | } 25 | 26 | // Mixin of the DNSDetector. 27 | func (DNSDetector) Mixin() []ent.Mixin { 28 | return []ent.Mixin{ 29 | TimeMixin{}, 30 | StatusMixin{}, 31 | DetectorMixin{}, 32 | } 33 | } 34 | 35 | // Edges of the DNSDetector. 36 | func (DNSDetector) Edges() []ent.Edge { 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /asset/asset.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // 静态态文件打包 16 | 17 | package asset 18 | 19 | import ( 20 | "embed" 21 | ) 22 | 23 | //go:embed * 24 | var assetFS embed.FS 25 | 26 | // GetFS get asset embed fs 27 | func GetFS() embed.FS { 28 | return assetFS 29 | } 30 | -------------------------------------------------------------------------------- /web/src/routes/router.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHashHistory } from "vue-router"; 2 | import { routes } from "./routes"; 3 | 4 | type loadingEvent = () => void; 5 | 6 | const noop = function () { 7 | return; 8 | }; 9 | 10 | let startEvent = noop; 11 | let finishEvent = noop; 12 | 13 | export function setLoadingEvent( 14 | start: loadingEvent, 15 | finish: loadingEvent 16 | ): void { 17 | startEvent = start; 18 | finishEvent = finish; 19 | } 20 | 21 | const router = createRouter({ 22 | history: createWebHashHistory(), 23 | routes, 24 | }); 25 | 26 | router.beforeEach(function (to, from, next) { 27 | if (!from || to.path !== from.path) { 28 | startEvent(); 29 | } 30 | next(); 31 | }); 32 | 33 | router.afterEach(function (to, from) { 34 | if (!from || to.path !== from.path) { 35 | finishEvent(); 36 | } 37 | }); 38 | export default router; 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pid 3 | 4 | vendor 5 | tmp 6 | tmp/* 7 | *.out 8 | *.log 9 | cybertect 10 | api.yml 11 | 12 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 13 | 14 | # dependencies 15 | web/node_modules 16 | web/.pnp 17 | .pnp.js 18 | 19 | # testing 20 | web/coverage 21 | 22 | # production 23 | web/build 24 | web/dist 25 | 26 | # misc 27 | .DS_Store 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | npm-debug.log* 34 | yarn-debug.log* 35 | yarn-error.log* 36 | 37 | 38 | .DS_Store 39 | node_modules 40 | /dist 41 | 42 | # local env files 43 | .env.local 44 | .env.*.local 45 | 46 | # Log files 47 | npm-debug.log* 48 | yarn-debug.log* 49 | yarn-error.log* 50 | 51 | # Editor directories and files 52 | .idea 53 | .vscode 54 | *.suo 55 | *.ntvs* 56 | *.njsproj 57 | *.sln 58 | *.sw? 59 | -------------------------------------------------------------------------------- /util/stack_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestGetStack(t *testing.T) { 24 | assert := assert.New(t) 25 | stacks := GetStack(5) 26 | assert.NotEmpty(stacks) 27 | } 28 | -------------------------------------------------------------------------------- /validate/configuration.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validate 16 | 17 | func init() { 18 | // 应用配置名称 19 | AddAlias("xConfigurationName", "min=2,max=20") 20 | AddAlias("xConfigurationCategory", "alphanum,min=2,max=30") 21 | AddAlias("xConfigurationData", "min=0,max=500") 22 | } 23 | -------------------------------------------------------------------------------- /web/src/views/configurations/Emails.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | import ExConfigEditorList from "../../components/ExConfigEditorList"; 3 | import { ConfigCategory } from "../../states/configs"; 4 | import { FormItem, FormItemTypes } from "../../components/ExFormInterface"; 5 | 6 | export default defineComponent({ 7 | name: "EmailConfigs", 8 | render() { 9 | const extraFormItems: FormItem[] = [ 10 | { 11 | name: "邮箱地址:", 12 | key: "data", 13 | type: FormItemTypes.TextArea, 14 | span: 24, 15 | placeholder: "请输入邮箱地址,多个地址以,分隔", 16 | }, 17 | ]; 18 | return ( 19 | 26 | ); 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /asset/dist/assets/MockTime-72101ce2.js: -------------------------------------------------------------------------------- 1 | import{g as m,E as c}from"./ExConfigEditor-da704c67.js";import{E as n}from"./ExLoading-572e76eb.js";import{s as f}from"./index-aeeb7070.js";import{C as i,c as p}from"./configs-5a9029c7.js";import{d,r as s,N as a}from"./ui-32540f7e.js";import{u as l}from"./naive-094f875f.js";import"./ExForm-cdb0faae.js";import"./detector-090ed643.js";import"./ExFormInterface-d830d527.js";import"./common-a7d1cc73.js";const x=d({name:"MockTime",setup(){const r=l(),o=s(0),e=s(!0);return(async()=>{e.value=!0;try{const t=await p();t.id&&(o.value=t.id)}catch(t){f(r,t)}finally{e.value=!1}})(),{id:o,processing:e}},render(){const{id:r,processing:o}=this;if(o)return a(n,null,null);const e=m({category:i.MockTime,name:i.MockTime});return e.push({name:"时间配置:",key:"data",type:"datetime",placeholder:"请选择要Mock的时间"}),a(c,{id:r,title:"添加/更新MockTime配置",description:"针对应用时间Mock,用于测试环境中调整应用时间",formItems:e},null)}});export{x as default}; 2 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | jobs: 8 | 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version-file: go.mod 18 | 19 | - name: Get dependencies 20 | run: 21 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin latest 22 | 23 | - name: Install 24 | run: make install 25 | 26 | - name: Generate 27 | run: make generate 28 | 29 | - name: Tidy 30 | run: make tidy 31 | - name: Run GoReleaser 32 | uses: goreleaser/goreleaser-action@v4 33 | with: 34 | version: latest 35 | args: release --clean --skip-validate 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GH_PAT }} -------------------------------------------------------------------------------- /asset/dist/assets/Ping-898e4802.js: -------------------------------------------------------------------------------- 1 | import{F as s}from"./ExFormInterface-d830d527.js";import{u as i,p,m as a,n as m,o as c}from"./detector-090ed643.js";import{c as u}from"./ExTable-dcbd254b.js";import{g as D,a as l,D as I}from"./Detector-ad8130de.js";import{d,N as f}from"./ui-32540f7e.js";import"./index-aeeb7070.js";import"./common-a7d1cc73.js";import"./naive-094f875f.js";import"./configs-5a9029c7.js";import"./ExForm-cdb0faae.js";import"./ExLoading-572e76eb.js";const N=d({name:"PingDetector",setup(){const t=i().pingDetectors;return{findByID:async e=>await p(e),pingDetectors:t}},render(){const{pingDetectors:t,findByID:r}=this,e=[u({key:"ips",title:"IP列表"})],o=[{type:s.DynamicInput,name:"IP列表:",key:"ips",span:12,placeholder:"请输入对应的IP地址",min:1}],n=D({ips:l("IP地址列表不能为空")});return f(I,{columns:e,fetch:a,findByID:r,updateByID:m,create:c,title:"Ping检测配置",description:"指定Ping检测IP列表,定时Ping该IP是否正常",formItems:o,data:t,rules:n},null)}});export{N as default}; 2 | -------------------------------------------------------------------------------- /asset/dist/assets/TCP-b7c9b1e1.js: -------------------------------------------------------------------------------- 1 | import{F as a}from"./ExFormInterface-d830d527.js";import{u as c,t as n,j as m,k as i,l as p}from"./detector-090ed643.js";import{c as d}from"./ExTable-dcbd254b.js";import{g as u,a as D,D as l}from"./Detector-ad8130de.js";import{d as f,N as y}from"./ui-32540f7e.js";import"./index-aeeb7070.js";import"./common-a7d1cc73.js";import"./naive-094f875f.js";import"./configs-5a9029c7.js";import"./ExForm-cdb0faae.js";import"./ExLoading-572e76eb.js";const g=f({name:"TCPDetector",setup(){const t=c().tcpDetectors;return{findByID:async e=>await n(e),tcpDetectors:t}},render(){const{tcpDetectors:t,findByID:r}=this,e=[d({key:"addrs",title:"检测地址"})],o=[{type:a.DynamicInput,name:"地址列表:",key:"addrs",span:12,placeholder:"请输入需要检测的地址,如(IP:Port)",min:1}],s=u({addrs:D("检测地址列表不能为空")});return y(l,{columns:e,fetch:m,findByID:r,updateByID:i,create:p,title:"TCP检测配置",description:"指定IP与端口,定时检测是否可用",formItems:o,data:t,rules:s},null)}});export{g as default}; 2 | -------------------------------------------------------------------------------- /asset/dist/assets/Configs-499aae23.js: -------------------------------------------------------------------------------- 1 | import{E as f}from"./ExConfigTable-74c02379.js";import{a as c}from"./configs-5a9029c7.js";import{s as d}from"./index-aeeb7070.js";import{E as m}from"./ExLoading-572e76eb.js";import{u as p,D as i,E as V}from"./naive-094f875f.js";import{d as g,r as o,N as e}from"./ui-32540f7e.js";import"./ExTable-dcbd254b.js";import"./ExFormInterface-d830d527.js";import"./common-a7d1cc73.js";const u="当前生效配置",x=g({name:"ConfigsView",setup(){const r=p(),n=o("所有配置"),t=o(""),a=o(!1);return{fetchValid:async()=>{if(!a.value){a.value=!0;try{const l=await c();t.value=JSON.stringify(l,null,2)}catch(l){d(r,l)}finally{a.value=!1}}},tab:n,currentValid:t,fetchingCurrentValid:a}},render(){const{tab:r,fetchValid:n,currentValid:t,fetchingCurrentValid:a}=this;return e(V,{defaultValue:r,onUpdateValue:s=>{s===u&&n()}},{default:()=>[e(i,{name:"所有配置"},{default:()=>[e(f,null,null)]}),e(i,{name:u},{default:()=>[a&&e(m,null,null),!a&&e("pre",null,[t])]})]})}});export{x as default}; 2 | -------------------------------------------------------------------------------- /helper/snappy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package helper 16 | 17 | import "github.com/golang/snappy" 18 | 19 | func SnappyEncode(data []byte) []byte { 20 | dst := []byte{} 21 | dst = snappy.Encode(dst, data) 22 | return dst 23 | } 24 | 25 | func SnappyDecode(buf []byte) ([]byte, error) { 26 | var dst []byte 27 | return snappy.Decode(dst, buf) 28 | } 29 | -------------------------------------------------------------------------------- /web/src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import { names } from "./routes"; 2 | import router from "./router"; 3 | import { LocationQueryRaw } from "vue-router"; 4 | 5 | export function goTo( 6 | name: string, 7 | params: { 8 | replace?: boolean; 9 | query?: LocationQueryRaw; 10 | } 11 | ): void { 12 | router.push({ 13 | name, 14 | replace: params.replace, 15 | query: params.query, 16 | }); 17 | } 18 | 19 | export function goToLogin(replace?: boolean): void { 20 | goTo(names.login, { 21 | replace: replace ?? false, 22 | }); 23 | } 24 | 25 | export function goToRegister(replace?: boolean): void { 26 | goTo(names.register, { 27 | replace: replace ?? false, 28 | }); 29 | } 30 | 31 | export function goToHome(replace?: boolean): void { 32 | goTo(names.home, { 33 | replace: replace ?? false, 34 | }); 35 | } 36 | 37 | export function goToProfile(replace?: boolean): void { 38 | goTo(names.profile, { 39 | replace: replace ?? false, 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine as webbuilder 2 | 3 | COPY . /cybertect 4 | RUN apk update \ 5 | && apk add make \ 6 | && cd /cybertect/web \ 7 | && yarn \ 8 | && yarn build \ 9 | && rm -rf node_modules 10 | 11 | FROM golang:1.23-alpine as builder 12 | 13 | COPY --from=webbuilder /cybertect /cybertect 14 | 15 | RUN apk update \ 16 | && apk add git make curl jq \ 17 | && cd /cybertect \ 18 | && rm -rf asset/dist \ 19 | && cp -rf web/dist asset/ \ 20 | && make install \ 21 | && make generate \ 22 | && make build 23 | 24 | FROM alpine 25 | 26 | EXPOSE 7001 27 | 28 | # tzdata 安装所有时区配置或可根据需要只添加所需时区 29 | 30 | RUN apk add --no-cache ca-certificates tzdata 31 | 32 | COPY --from=builder /cybertect/cybertect /usr/local/bin/cybertect 33 | COPY --from=builder /cybertect/entrypoint.sh /entrypoint.sh 34 | 35 | HEALTHCHECK --timeout=10s --interval=10s CMD [ "wget", "http://127.0.0.1:7001/ping", "-q", "-O", "-"] 36 | 37 | CMD ["cybertect"] 38 | 39 | ENTRYPOINT ["/entrypoint.sh"] 40 | -------------------------------------------------------------------------------- /service/ip_blocker_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestIPBlocker(t *testing.T) { 24 | assert := assert.New(t) 25 | assert.Nil(ResetIPBlocker([]string{ 26 | "1.1.1.1", 27 | })) 28 | assert.True(IsBlockIP("1.1.1.1")) 29 | assert.False(IsBlockIP("1.1.1.2")) 30 | } 31 | -------------------------------------------------------------------------------- /schema/httpdetector.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | // HTTPDetector holds the schema definition for the HTTP entity. 9 | type HTTPDetector struct { 10 | ent.Schema 11 | } 12 | 13 | // Fields of the HTTP. 14 | func (HTTPDetector) Fields() []ent.Field { 15 | return []ent.Field{ 16 | field.Strings("ips"). 17 | Comment("IP列表"), 18 | field.String("url"). 19 | NotEmpty(). 20 | Comment("测试URL"), 21 | field.String("script"). 22 | Optional(). 23 | Comment("检测脚本"), 24 | field.Strings("proxies"). 25 | Optional(). 26 | Comment("代理列表"), 27 | field.Int8("randomQueryString"). 28 | Optional(). 29 | Comment("随机query string"), 30 | } 31 | } 32 | 33 | // Mixin http mixin 34 | func (HTTPDetector) Mixin() []ent.Mixin { 35 | return []ent.Mixin{ 36 | TimeMixin{}, 37 | StatusMixin{}, 38 | DetectorMixin{}, 39 | } 40 | } 41 | 42 | // Edges of the HTTP. 43 | func (HTTPDetector) Edges() []ent.Edge { 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /helper/redis_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package helper 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestRedisStats(t *testing.T) { 24 | assert := assert.New(t) 25 | m := RedisStats() 26 | assert.NotNil(m) 27 | } 28 | 29 | func TestRedisPing(t *testing.T) { 30 | assert := assert.New(t) 31 | err := RedisPing() 32 | assert.Nil(err) 33 | } 34 | -------------------------------------------------------------------------------- /router_concurrency/router_concurrency_test.go: -------------------------------------------------------------------------------- 1 | package routerconcurrency 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/vicanso/elton" 8 | ) 9 | 10 | func TestRouterLimiter(t *testing.T) { 11 | assert := assert.New(t) 12 | 13 | InitLimiter([]elton.RouterInfo{ 14 | { 15 | Method: "GET", 16 | Route: "/", 17 | }, 18 | { 19 | Method: "GET", 20 | Route: "/users/me", 21 | }, 22 | }) 23 | rc := GetLimiter() 24 | key := "GET /" 25 | count, max := rc.IncConcurrency(key) 26 | assert.Equal(uint32(1), count) 27 | assert.Equal(uint32(0), max) 28 | 29 | assert.Equal(uint32(1), rc.GetConcurrency(key)) 30 | 31 | rc.DecConcurrency(key) 32 | assert.Equal(uint32(0), rc.GetConcurrency(key)) 33 | 34 | // 重置路由并发配置 35 | Update([]string{ 36 | `{ 37 | "route": "/", 38 | "method": "GET", 39 | "max": 10, 40 | "rateLimit": "100/s" 41 | }`, 42 | }) 43 | count, max = rc.IncConcurrency(key) 44 | assert.Equal(uint32(1), count) 45 | assert.Equal(uint32(10), max) 46 | } 47 | -------------------------------------------------------------------------------- /util/env_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | "github.com/vicanso/cybertect/config" 22 | ) 23 | 24 | func TestENV(t *testing.T) { 25 | assert := assert.New(t) 26 | 27 | env := config.GetENV() 28 | assert.Equal(env == config.Dev, IsDevelopment()) 29 | assert.Equal(env == config.Test, IsTest()) 30 | assert.False(IsProduction()) 31 | } 32 | -------------------------------------------------------------------------------- /web/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | import vueJsx from "@vitejs/plugin-vue-jsx"; 4 | import VitePluginLinaria from 'vite-plugin-linaria'; 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | base: process.env.STATIC || "/", 9 | plugins: [ 10 | vue(), 11 | vueJsx(), 12 | VitePluginLinaria(), 13 | ], 14 | build: { 15 | rollupOptions: { 16 | output: { 17 | manualChunks: { 18 | common: [ 19 | "axios", 20 | "dayjs", 21 | "localforage", 22 | "pako", 23 | ], 24 | ui: [ 25 | "vue", 26 | "vue-router", 27 | ], 28 | naive: [ 29 | "naive-ui", 30 | ] 31 | }, 32 | }, 33 | }, 34 | }, 35 | server: { 36 | proxy: { 37 | "/api": { 38 | target: "http://localhost:7001", 39 | rewrite: (path) => path.replace(/^\/api/, ""), 40 | }, 41 | }, 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /web/src/storages/local.ts: -------------------------------------------------------------------------------- 1 | import localforage from "localforage"; 2 | 3 | const store = localforage.createInstance({ 4 | name: "cybertect", 5 | }); 6 | 7 | class LocalStorage { 8 | private key: string; 9 | private data: Record; 10 | constructor(key: string) { 11 | this.data = {}; 12 | this.key = key; 13 | } 14 | getData(): Record { 15 | return Object.assign({}, this.data); 16 | } 17 | // load 加载数据 18 | async load(): Promise> { 19 | const data = await store.getItem(this.key); 20 | if (!data) { 21 | return {}; 22 | } 23 | const str = data as string; 24 | this.data = JSON.parse(str || "{}"); 25 | return this.getData(); 26 | } 27 | // set 设置数据 28 | async set(key: string, value: string | number | boolean) { 29 | const data = this.data; 30 | data[key] = value; 31 | await store.setItem(this.key, JSON.stringify(data)); 32 | return this.getData(); 33 | } 34 | } 35 | 36 | export const settingStorage = new LocalStorage("settings"); 37 | -------------------------------------------------------------------------------- /controller/user_doc.go: -------------------------------------------------------------------------------- 1 | // +build swagger 2 | // 用户相关接口文档 3 | 4 | package controller 5 | 6 | import "github.com/vicanso/cybertect/ent" 7 | 8 | // 用户列表响应 9 | // swagger:response apiUserListResponse 10 | type apiUserListResponse struct { 11 | // in: body 12 | Body *userListResp 13 | } 14 | 15 | // 用户信息查询参数 16 | // swagger:parameters userList 17 | type apiUserListParams struct { 18 | userListParams 19 | } 20 | 21 | // 用户登录Token响应 22 | // swagger:response apiUserLoginTokenResponse 23 | type apiUserLoginTokenResponse struct { 24 | // in: body 25 | Body *userLoginTokenResp 26 | } 27 | 28 | // 用户信息 29 | // swagger:response apiUserInfoResponse 30 | type apiUserInfoResponse struct { 31 | // in: body 32 | Body *userInfoResp 33 | } 34 | 35 | // 用户登录与注册参数 36 | // swagger:parameters userRegister userLogin 37 | type apiUserRegisterLoginParams struct { 38 | // in: body 39 | Body *userRegisterLoginParams 40 | } 41 | 42 | // 用户注册响应 43 | // swagger:response apiUserRegisterResponse 44 | type apiUserRegisterResponse struct { 45 | // in: body 46 | Body *ent.User 47 | } 48 | -------------------------------------------------------------------------------- /util/env.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import "github.com/vicanso/cybertect/config" 18 | 19 | // IsDevelopment 判断是否开发环境 20 | func IsDevelopment() bool { 21 | return config.GetENV() == config.Dev 22 | } 23 | 24 | // IsTest 判断是否测试环境 25 | func IsTest() bool { 26 | return config.GetENV() == config.Test 27 | } 28 | 29 | // IsProduction 判断是否生产环境 30 | func IsProduction() bool { 31 | return config.GetENV() == config.Production 32 | } 33 | -------------------------------------------------------------------------------- /helper/ent_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package helper 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestEnt(t *testing.T) { 24 | assert := assert.New(t) 25 | 26 | assert.NotNil(EntGetClient()) 27 | 28 | assert.Nil(EntInitSchema()) 29 | 30 | assert.Nil(EntPing()) 31 | } 32 | 33 | func TestEntGetStats(t *testing.T) { 34 | assert := assert.New(t) 35 | stats := EntGetStats() 36 | assert.NotNil(stats) 37 | } 38 | -------------------------------------------------------------------------------- /helper/influxdb_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package helper 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | ) 21 | 22 | func TestInfluxWrite(t *testing.T) { 23 | GetInfluxDB().Write("test", map[string]string{ 24 | "type": "vip", 25 | }, map[string]interface{}{ 26 | "name": "test", 27 | "count": 1, 28 | }) 29 | GetInfluxDB().Write("test", map[string]string{ 30 | "type": "vip", 31 | }, map[string]interface{}{ 32 | "name": "test", 33 | "count": 1, 34 | }, time.Now()) 35 | } 36 | -------------------------------------------------------------------------------- /web/src/Root.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent } from "vue"; 2 | import { 3 | darkTheme, 4 | NConfigProvider, 5 | NDialogProvider, 6 | NGlobalStyle, 7 | NLoadingBarProvider, 8 | NMessageProvider, 9 | NNotificationProvider, 10 | zhCN, 11 | } from "naive-ui"; 12 | import useCommonState from "./states/common"; 13 | 14 | import App from "./App"; 15 | 16 | export default defineComponent({ 17 | name: "RootPage", 18 | setup() { 19 | const { settings } = useCommonState(); 20 | return { 21 | settings, 22 | }; 23 | }, 24 | render() { 25 | const isDark = this.settings.theme === "dark"; 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /validate/detector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validate 16 | 17 | func init() { 18 | AddAlias("xDetectorName", "min=1,max=30") 19 | AddAlias("xDetectorDesc", "min=1,max=300") 20 | AddAlias("xDetectorReceiver", "email") 21 | AddAlias("xDetectorTaskID", "number") 22 | // 检测结果 23 | AddAlias("xDetectorResult", "oneof=1 2") 24 | AddAlias("xDetectorCategories", "min=1,max=30") 25 | 26 | AddAlias("xDetectorDatabaseURI", "min=1,max=500") 27 | AddAlias("xDetectorTLSPemData", "ascii,min=1,max=10240") 28 | } 29 | -------------------------------------------------------------------------------- /util/number.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "fmt" 19 | "math" 20 | "strconv" 21 | ) 22 | 23 | // 可根据需求调整相差值的判断 24 | const tolerance = 1e-6 25 | 26 | // AlmostEqual returns true is abs(a - b) < 1e-6 27 | func AlmostEqual(a, b float64) bool { 28 | return math.Abs(a-b) <= tolerance 29 | } 30 | 31 | // ToFixed formats float64 to string 32 | func ToFixed(value float64, precision int) string { 33 | str := "%." + strconv.Itoa(precision) + "f" 34 | return fmt.Sprintf(str, value) 35 | } 36 | -------------------------------------------------------------------------------- /middleware/stats_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "net/http/httptest" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/vicanso/elton" 23 | ) 24 | 25 | func TestNewStats(t *testing.T) { 26 | assert := assert.New(t) 27 | // 仅测试执行,不检查数据 28 | fn := NewStats() 29 | req := httptest.NewRequest("GET", "/", nil) 30 | c := elton.NewContext(nil, req) 31 | c.Next = func() error { 32 | return nil 33 | } 34 | err := fn(c) 35 | assert.Nil(err) 36 | } 37 | -------------------------------------------------------------------------------- /cs/cs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package cs 16 | 17 | import "regexp" 18 | 19 | const ( 20 | // CID context id 21 | CID = "cid" 22 | // UserSession user session 23 | UserSession = "userSession" 24 | ) 25 | 26 | const ( 27 | // MagicalCaptcha magical captcha(for test only) 28 | MagicalCaptcha = "0145" 29 | ) 30 | 31 | const ( 32 | // ResultSuccess result success 33 | ResultSuccess = iota 34 | // ResultFail result fail 35 | ResultFail 36 | ) 37 | 38 | // ***处理 39 | var MaskRegExp = regexp.MustCompile(`(?i)password`) 40 | -------------------------------------------------------------------------------- /service/ip_blocker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "github.com/vicanso/ips" 19 | ) 20 | 21 | var blockIPS = ips.New() 22 | 23 | // ResetIPBlocker 重置IP拦截器的IP列表 24 | func ResetIPBlocker(ipList []string) error { 25 | return blockIPS.Replace(ipList...) 26 | } 27 | 28 | // IsBlockIP 判断该IP是否有需要拦截 29 | func IsBlockIP(ip string) bool { 30 | return blockIPS.Contains(ip) 31 | } 32 | 33 | // GetIPBlockList 获取block的ip地址列表 34 | func GetIPBlockList() []string { 35 | return blockIPS.Strings() 36 | } 37 | -------------------------------------------------------------------------------- /schema/tcpdetectorresult.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | // TCPDetectorResult holds the schema definition for the TCPDetectorResult entity. 9 | type TCPDetectorResult struct { 10 | ent.Schema 11 | } 12 | 13 | type TCPDetectorSubResult struct { 14 | Result DetectorResult `json:"result"` 15 | Addr string `json:"addr"` 16 | Duration int `json:"duration"` 17 | Message string `json:"message"` 18 | } 19 | 20 | type TCPDetectorSubResults []*TCPDetectorSubResult 21 | 22 | // Fields of the TCPDetectorResult. 23 | func (TCPDetectorResult) Fields() []ent.Field { 24 | return []ent.Field{ 25 | field.Strings("addrs"). 26 | Comment("检测地址"), 27 | field.JSON("results", TCPDetectorSubResults{}). 28 | Comment("检测结果列表"), 29 | } 30 | } 31 | 32 | // Mixin of the TCPDetectorResult. 33 | func (TCPDetectorResult) Mixin() []ent.Mixin { 34 | return []ent.Mixin{ 35 | TimeMixin{}, 36 | DetectorResultMixin{}, 37 | } 38 | } 39 | 40 | // Edges of the TCPDetectorResult. 41 | func (TCPDetectorResult) Edges() []ent.Edge { 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /asset/dist/assets/ExConfigEditorList-10d35f0e.js: -------------------------------------------------------------------------------- 1 | import{g,E as f}from"./ExConfigEditor-da704c67.js";import{E}from"./ExConfigTable-74c02379.js";import{M as t}from"./index-aeeb7070.js";import{d as x,r as u,N as o,c as y}from"./ui-32540f7e.js";import{d as D,r as I}from"./naive-094f875f.js";const C="apayaz1",b=x({name:"ExConfigEditorList",props:{listTitle:{type:String,required:!0},editorTitle:{type:String,required:!0},editorDescription:{type:String,required:!0},category:{type:String,required:!0},extraFormItems:{type:Array,default:()=>[]}},setup(){const e=u(t.List);return{updatedID:u(0),toggle:s=>{e.value=s},mode:e}},render(){const{listTitle:e,editorTitle:d,category:i,editorDescription:s,extraFormItems:p}=this.$props,{mode:l,toggle:r,updatedID:m}=this;if(l===t.List)return o(I,{title:e},{default:()=>[o(E,{category:i,onUpdate:a=>{this.updatedID=a,r(t.Update)}},null),o(D,{size:"large",class:C,onClick:()=>{this.updatedID=0,r(t.Add)}},{default:()=>[y("增加配置")]})]});const n=g({category:i});return p.forEach(a=>{const c=Object.assign({},a);n.push(c)}),o(f,{title:d,description:s,id:m,formItems:n,onSubmitDone:()=>{r(t.List)},onBack:()=>{r(t.List)}},null)}});export{b as E}; 2 | -------------------------------------------------------------------------------- /detector/detector_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package detector 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestIsMatchAlarmCount(t *testing.T) { 24 | assert := assert.New(t) 25 | 26 | assert.True(isMatchAlarmCount(1)) 27 | assert.True(isMatchAlarmCount(2)) 28 | assert.True(isMatchAlarmCount(3)) 29 | assert.False(isMatchAlarmCount(4)) 30 | assert.True(isMatchAlarmCount(10)) 31 | assert.False(isMatchAlarmCount(61)) 32 | assert.True(isMatchAlarmCount(120)) 33 | } 34 | -------------------------------------------------------------------------------- /util/map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestMergeMapStringInterface(t *testing.T) { 24 | assert := assert.New(t) 25 | 26 | target := MergeMapStringInterface(map[string]interface{}{ 27 | "a": 1, 28 | }, map[string]interface{}{ 29 | "b": true, 30 | }, map[string]interface{}{ 31 | "c": "2", 32 | }) 33 | 34 | assert.Equal(1, target["a"]) 35 | assert.Equal(true, target["b"]) 36 | assert.Equal("2", target["c"]) 37 | } 38 | -------------------------------------------------------------------------------- /router/router.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package router 16 | 17 | import ( 18 | "github.com/vicanso/elton" 19 | ) 20 | 21 | var ( 22 | // groupList 路由组列表 23 | groupList = make([]*elton.Group, 0) 24 | ) 25 | 26 | // NewGroup new router group 27 | func NewGroup(path string, handlerList ...elton.Handler) *elton.Group { 28 | // 如果配置文件中有配置路由 29 | g := elton.NewGroup(path, handlerList...) 30 | groupList = append(groupList, g) 31 | return g 32 | } 33 | 34 | // GetGroups get all groups 35 | func GetGroups() []*elton.Group { 36 | return groupList 37 | } 38 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: default test test-cover dev generate hooks lint-web doc 2 | 3 | # for dev 4 | dev: 5 | air -c .air.toml 6 | dev-debug: 7 | LOG_LEVEL=0 make dev 8 | doc: 9 | CGO_ENABLED=0 swagger generate spec -t swagger -o ./asset/api.yml && swagger validate ./asset/api.yml 10 | 11 | test: 12 | go test -race -cover ./... 13 | 14 | install: 15 | go get -d entgo.io/ent/cmd/entc@v0.14.1 16 | 17 | build-web: 18 | cd web && npm install && npm run build && rm -rf node_modules && cd .. && rm -rf asset/dist && cp -rf web/dist asset/ 19 | 20 | generate: 21 | go run entgo.io/ent/cmd/ent generate --feature sql/modifier --feature privacy --feature intercept ./schema --template ./template --target ./ent 22 | 23 | describe: 24 | entc describe ./schema 25 | 26 | test-cover: 27 | go test -race -coverprofile=test.out ./... && go tool cover --html=test.out 28 | 29 | list-mod: 30 | go list -m -u all 31 | 32 | tidy: 33 | go mod tidy 34 | 35 | build: 36 | go build -ldflags "-X main.Version=0.0.1 -X 'main.BuildedAt=`date`'" -o cybertect 37 | 38 | 39 | lint: 40 | golangci-lint run 41 | 42 | lint-web: 43 | cd web && yarn lint 44 | 45 | hooks: 46 | cp hooks/* .git/hooks/ -------------------------------------------------------------------------------- /asset/dist/assets/DNS-16d14de9.js: -------------------------------------------------------------------------------- 1 | import{F as o}from"./ExFormInterface-d830d527.js";import{u as m,d as c,e as p,f as D,i as d}from"./detector-090ed643.js";import{c as n}from"./ExTable-dcbd254b.js";import{g as l,n as u,a,D as y}from"./Detector-ad8130de.js";import{d as I,N as f}from"./ui-32540f7e.js";import"./index-aeeb7070.js";import"./common-a7d1cc73.js";import"./naive-094f875f.js";import"./configs-5a9029c7.js";import"./ExForm-cdb0faae.js";import"./ExLoading-572e76eb.js";const L=I({name:"DNSDetector",setup(){const e=m().dnsDetectors;return{findByID:async t=>await c(t),dnsDetectors:e}},render(){const{dnsDetectors:e,findByID:s}=this,t=[{title:"域名",key:"host"},n({key:"ips",title:"IP列表"}),n({key:"servers",title:"DNS服务器"})],r=[{name:"域名",key:"host",placeholder:"请输入要检测的域名",span:8},{type:o.DynamicInput,name:"IP列表:",key:"ips",span:8,placeholder:"请输入对应的IP解析",min:1},{type:o.DynamicInput,name:"DNS服务器:",key:"servers",span:8,placeholder:"请输入DNS服务器",min:1}],i=l({host:u("域名不能为空"),servers:a("DNS服务器列表不能为空"),ips:a("域名解析IP地址列表不能为空")});return f(y,{columns:t,fetch:p,findByID:s,updateByID:D,create:d,title:"DNS检测",description:"指定DNS服务器检测解析IP是否正确",formItems:r,data:e,rules:i},null)}});export{L as default}; 2 | -------------------------------------------------------------------------------- /helper/snappy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package helper 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestSnappy(t *testing.T) { 24 | assert := assert.New(t) 25 | data := []byte("Snappy 是一个 C++ 的用来压缩和解压缩的开发包。其目标不是最大限度压缩或者兼容其他压缩格式,而是旨在提供高速压缩速度和合理的压缩率。Snappy 比 zlib 更快,但文件相对要大 20% 到 100%。在 64位模式的 Core i7 处理器上,可达每秒 250~500兆的压缩速度。") 26 | buf := SnappyEncode(data) 27 | assert.True(len(buf) < len(data)) 28 | 29 | result, err := SnappyDecode(buf) 30 | assert.Nil(err) 31 | assert.Equal(data, result) 32 | } 33 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "scripts": { 4 | "format": "prettier --write src/*.ts src/*.tsx src/**/*.ts src/**/*.tsx src/**/**/*.ts src/**/**/*.tsx", 5 | "lint": "eslint . --ext .js,.tsx,.ts --fix && vue-tsc --noEmit", 6 | "dev": "vite", 7 | "build": "STATIC=/static vite build", 8 | "serve": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@linaria/core": "^3.0.0-beta.15", 12 | "@vicons/fa": "^0.12.0", 13 | "axios": "^1.4.0", 14 | "dayjs": "^1.11.8", 15 | "localforage": "^1.10.0", 16 | "lodash-es": "^4.17.21", 17 | "naive-ui": "^2.34.4", 18 | "pako": "^2.1.0", 19 | "vue": "^3.3.4", 20 | "vue-router": "^4.2.2" 21 | }, 22 | "devDependencies": { 23 | "@types/pako": "^2.0.0", 24 | "@typescript-eslint/eslint-plugin": "^5.59.8", 25 | "@vitejs/plugin-vue": "^4.2.3", 26 | "@vitejs/plugin-vue-jsx": "^3.0.1", 27 | "@vue/compiler-sfc": "^3.3.4", 28 | "eslint": "^8.41.0", 29 | "eslint-plugin-vue": "^9.14.1", 30 | "prettier": "^2.8.8", 31 | "typescript": "^5.1.3", 32 | "vite": "^4.3.9", 33 | "vite-plugin-linaria": "1.0.0", 34 | "vue-tsc": "^1.6.5" 35 | } 36 | } -------------------------------------------------------------------------------- /asset/dist/assets/ExConfigTable-74c02379.js: -------------------------------------------------------------------------------- 1 | import{E as f,n as l,b as u}from"./ExTable-dcbd254b.js";import{B as i}from"./index-aeeb7070.js";import{g as d,u as p,h as g,b as m}from"./configs-5a9029c7.js";import{d as y,E as C,N as b,i as h}from"./ui-32540f7e.js";function k(t){return typeof t=="function"||Object.prototype.toString.call(t)==="[object Object]"&&!h(t)}function E(){return[{title:"名称",key:"name"},u("data"),{title:"分类",key:"category"},{title:"状态",key:"status",render(t){return t.status===m.Enabled?"启用":"禁用"}},{title:"创建者",key:"owner"},{title:"配置生效时间",key:"startedAt",render(t){return i(t.startedAt)}},{title:"配置失效时间",key:"endedAt",render(t){return i(t.endedAt)}},{title:"配置描述",key:"description",width:100,ellipsis:{tooltip:!0}}]}function s(){}const N=y({name:"ConfigTable",props:{title:{type:String,default:""},category:{type:String,default:()=>""},onUpdate:{type:Function,default:s}},setup(t){const{configs:e}=p(),n=()=>g({category:t.category});return C(()=>{d()}),{fetchConfigs:n,configs:e}},render(){const{title:t,onUpdate:e}=this.$props,{configs:n,fetchConfigs:a,$slots:o}=this,r=E();return e!==s&&r.push(l(c=>{e(c.id)})),b(f,{title:t,columns:r,data:n,fetch:a},k(o)?o:{default:()=>[o]})}});export{N as E}; 2 | -------------------------------------------------------------------------------- /profiler/profiler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package profiler 16 | 17 | import ( 18 | "bytes" 19 | "time" 20 | 21 | "github.com/felixge/fgprof" 22 | "github.com/vicanso/hes" 23 | ) 24 | 25 | func GetProf(d time.Duration) (*bytes.Buffer, error) { 26 | // 禁止拉取超过1分钟的prof 27 | if d > 1*time.Minute { 28 | return nil, hes.New("duration should be less than 1m") 29 | } 30 | result := &bytes.Buffer{} 31 | done := fgprof.Start(result, fgprof.FormatPprof) 32 | time.Sleep(d) 33 | err := done() 34 | if err != nil { 35 | return nil, err 36 | } 37 | return result, nil 38 | } 39 | -------------------------------------------------------------------------------- /service/performance_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package service 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestPerformanceConcurrency(t *testing.T) { 25 | assert := assert.New(t) 26 | assert.Equal(int32(1), IncreaseConcurrency()) 27 | assert.Equal(int32(1), GetConcurrency()) 28 | assert.Equal(int32(0), DecreaseConcurrency()) 29 | } 30 | 31 | func TestGetPerformance(t *testing.T) { 32 | assert := assert.New(t) 33 | p := GetPerformance(context.Background()) 34 | assert.NotEmpty(p.MemSys) 35 | } 36 | -------------------------------------------------------------------------------- /session/session_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http/httptest" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/vicanso/elton" 23 | se "github.com/vicanso/elton-session" 24 | ) 25 | 26 | func TestNewSession(t *testing.T) { 27 | assert := assert.New(t) 28 | 29 | req := httptest.NewRequest("GET", "/", nil) 30 | c := elton.NewContext(nil, req) 31 | c.Next = func() error { 32 | return nil 33 | } 34 | fn := New() 35 | err := fn(c) 36 | assert.Nil(err) 37 | assert.NotNil(c.Get(se.Key)) 38 | } 39 | -------------------------------------------------------------------------------- /asset/dist/assets/RequestConcurrencies-160bdd88.js: -------------------------------------------------------------------------------- 1 | import{E as n}from"./ExConfigEditorList-10d35f0e.js";import{F as o}from"./ExFormInterface-d830d527.js";import{E as a}from"./ExLoading-572e76eb.js";import{G as m,s as i,E as p}from"./index-aeeb7070.js";import{C as c}from"./configs-5a9029c7.js";import{d as u,f as l,N as s}from"./ui-32540f7e.js";import{u as d}from"./naive-094f875f.js";import"./ExConfigEditor-da704c67.js";import"./ExForm-cdb0faae.js";import"./detector-090ed643.js";import"./ExConfigTable-74c02379.js";import"./ExTable-dcbd254b.js";import"./common-a7d1cc73.js";const H=u({name:"RequestCOncurrencies",setup(){const{requestInstances:e}=p(),r=d();return l(async()=>{try{await m()}catch(t){i(r,t)}}),{requestInstances:e}},render(){const{requestInstances:e}=this;if(e.processing)return s(a,null,null);const r=[{type:o.Blank,name:"",key:""},{name:"实例:",key:"data.name",type:o.Select,placeholder:"请选择限制并发数的实例",options:e.items.map(t=>({label:t.name,value:t.name}))},{name:"并发数:",key:"data.max",type:o.InputNumber,placeholder:"请输入并发限制"}];return s(n,{listTitle:"HTTP请求实例并发配置",editorTitle:"添加/更新HTTP请求实例并发限制",editorDescription:"设置各HTTP请求实例的并发请求数",category:c.RequestConcurrency,extraFormItems:r},null)}});export{H as default}; 2 | -------------------------------------------------------------------------------- /util/number_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/assert" 21 | ) 22 | 23 | func TestAlmostEqual(t *testing.T) { 24 | assert := assert.New(t) 25 | assert.True(AlmostEqual(0.1+0.2+9e-7, 0.3)) 26 | assert.False(AlmostEqual(0.2+1e-5, 0.2)) 27 | } 28 | 29 | func TestToFixed(t *testing.T) { 30 | assert := assert.New(t) 31 | 32 | assert.Equal("1.12", ToFixed(1.1221132, 2)) 33 | assert.Equal("1.13", ToFixed(1.129, 2)) 34 | assert.Equal("1.10", ToFixed(1.1, 2)) 35 | assert.Equal("1", ToFixed(1.1, 0)) 36 | } 37 | -------------------------------------------------------------------------------- /schema/dnsdetectorresult.go: -------------------------------------------------------------------------------- 1 | package schema 2 | 3 | import ( 4 | "entgo.io/ent" 5 | "entgo.io/ent/schema/field" 6 | ) 7 | 8 | // DNSDetectorResult holds the schema definition for the DNSDetectorResult entity. 9 | type DNSDetectorResult struct { 10 | ent.Schema 11 | } 12 | 13 | type DNSDetectorSubResult struct { 14 | Result DetectorResult `json:"result,omitempty"` 15 | IPS []string `json:"ips,omitempty"` 16 | Server string `json:"server,omitempty"` 17 | Duration int `json:"duration,omitempty"` 18 | Message string `json:"message,omitempty"` 19 | } 20 | type DNSDetectorSubResults []*DNSDetectorSubResult 21 | 22 | // Fields of the DNSDetectorResult. 23 | func (DNSDetectorResult) Fields() []ent.Field { 24 | return []ent.Field{ 25 | field.String("host"). 26 | Comment("检测Host"), 27 | field.JSON("results", DNSDetectorSubResults{}). 28 | Comment("检测结果列表"), 29 | } 30 | } 31 | 32 | // Mixin of the DNSDetectorResult 33 | func (DNSDetectorResult) Mixin() []ent.Mixin { 34 | return []ent.Mixin{ 35 | TimeMixin{}, 36 | DetectorResultMixin{}, 37 | } 38 | } 39 | 40 | // Edges of the DNSDetectorResult. 41 | func (DNSDetectorResult) Edges() []ent.Edge { 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /asset/dist/assets/PingResult-e88b1dd9.js: -------------------------------------------------------------------------------- 1 | import{u as i,z as u}from"./detector-090ed643.js";import{d as o,c as l}from"./ExTable-dcbd254b.js";import{N as c}from"./index-aeeb7070.js";import{E as m,n as p}from"./ExDetectorResultTable-2191f62a.js";import{d,N as t}from"./ui-32540f7e.js";import{M as f,C as g,r as k}from"./naive-094f875f.js";import"./ExFormInterface-d830d527.js";import"./common-a7d1cc73.js";const y="p15nhdzn",I=d({name:"PingResult",setup(){const e=async s=>{await u(s)};return{pingDetectorResults:i().pingDetectorResults,fetch:e}},render(){const{pingDetectorResults:e,fetch:s}=this,a=[{title:"名称",key:"taskName"},o({title:"结果",key:"result.desc"}),l({title:"检测IP",key:"ips"}),{title:"最大耗时(ms)",key:"maxDuration"},l({title:"失败信息",key:"messages"}),{title:"更新于",key:"updatedAt",render(r){return c(r.updatedAt)}},{title:"更多",key:"",render(r){const n=[{title:"IP",key:"ip",fixed:"left",width:200},o({title:"结果",key:"result.desc"}),{title:"耗时(ms)",key:"duration"},{title:"失败信息",key:"message"}];return t(g,{placement:"left-end",trigger:"click"},{default:()=>[t("div",{class:y},[t(f,{columns:n,data:r.results},null)])],...{trigger:p}})}}];return t(k,{title:"Ping检测结果"},{default:()=>[t(m,{columns:a,data:e,fetch:s,category:"ping"},null)]})}});export{I as default}; 2 | -------------------------------------------------------------------------------- /asset/dist/assets/TCPResult-cc687576.js: -------------------------------------------------------------------------------- 1 | import{u as c,y as i}from"./detector-090ed643.js";import{d as o,c as a}from"./ExTable-dcbd254b.js";import{N as u}from"./index-aeeb7070.js";import{E as m,n as d}from"./ExDetectorResultTable-2191f62a.js";import{d as p,N as t}from"./ui-32540f7e.js";import{M as f,C as y,r as k}from"./naive-094f875f.js";import"./ExFormInterface-d830d527.js";import"./common-a7d1cc73.js";const C="p1m3677v",x=p({name:"TCPResult",setup(){const e=async s=>{await i(s)};return{tcpDetectorResults:c().tcpDetectorResults,fetch:e}},render(){const{tcpDetectorResults:e,fetch:s}=this,l=[{title:"名称",key:"taskName"},o({title:"结果",key:"result.desc"}),a({title:"检测地址",key:"addrs"}),{title:"最大耗时(ms)",key:"maxDuration"},a({title:"失败信息",key:"messages"}),{title:"更新于",key:"updatedAt",render(r){return u(r.updatedAt)}},{title:"更多",key:"",render(r){const n=[{title:"地址",key:"addr",fixed:"left",width:200},o({title:"结果",key:"result.desc"}),{title:"耗时(ms)",key:"duration"},{title:"失败信息",key:"message"}];return t(y,{placement:"left-end",trigger:"click"},{default:()=>[t("div",{class:C},[t(f,{columns:n,data:r.results},null)])],...{trigger:d}})}}];return t(k,{title:"TCP检测结果"},{default:()=>[t(m,{columns:l,data:e,fetch:s,category:"tcp"},null)]})}});export{x as default}; 2 | -------------------------------------------------------------------------------- /asset/dist/assets/DatabaseResult-963f24cb.js: -------------------------------------------------------------------------------- 1 | import{u as n,B as i}from"./detector-090ed643.js";import{d as r,c as l}from"./ExTable-dcbd254b.js";import{E as c,n as m}from"./ExDetectorResultTable-2191f62a.js";import{N as d}from"./index-aeeb7070.js";import{d as p,N as e}from"./ui-32540f7e.js";import{M as f,C as k,r as y}from"./naive-094f875f.js";import"./ExFormInterface-d830d527.js";import"./common-a7d1cc73.js";const D="pux6hxu",L=p({name:"DatabaseResult",setup(){const t=async a=>{await i(a)};return{databaseDetectorResults:n().databaseDetectorResults,fetch:t}},render(){const{databaseDetectorResults:t,fetch:a}=this,o=[{title:"名称",key:"taskName"},r({title:"结果",key:"result.desc"}),l({title:"连接串列表",key:"uris"}),{title:"最大耗时(ms)",key:"maxDuration"},l({title:"失败信息",key:"messages"}),{title:"更新于",key:"updatedAt",render(s){return d(s.updatedAt)}},{title:"更多",key:"",render(s){const u=[{title:"连接串",key:"uri"},r({title:"结果",key:"result.desc"}),{title:"耗时(ms)",key:"duration"},{title:"失败信息",key:"message"}];return e(k,{placement:"left-end",trigger:"click"},{default:()=>[e("div",{class:D},[e(f,{columns:u,data:s.results},null)])],...{trigger:m}})}}];return e(y,{title:"Database检测结果"},{default:()=>[e(c,{columns:o,data:t,fetch:a,category:"database"},null)]})}});export{L as default}; 2 | -------------------------------------------------------------------------------- /asset/dist/assets/Logins-188ecd5f.js: -------------------------------------------------------------------------------- 1 | import{E as o}from"./ExTable-dcbd254b.js";import{p as n,q as s,k as r,r as l}from"./index-aeeb7070.js";import{F as a}from"./ExFormInterface-d830d527.js";import{d,E as u,N as c}from"./ui-32540f7e.js";import"./naive-094f875f.js";import"./common-a7d1cc73.js";function p(){return[{title:"账户",key:"account",width:120,ellipsis:{tooltip:!0}},{title:"IP",key:"ip",width:120,ellipsis:{tooltip:!0}},{title:"定位",key:"location",width:150},{title:"运营商",key:"isp",width:80},{title:"Track ID",key:"trackID"},{title:"Session ID",key:"sessionID"},{title:"Forwarded For",key:"xForwardedFor",width:140,ellipsis:{tooltip:!0}},{title:"User Agent",key:"userAgent",width:120,ellipsis:{tooltip:!0}}]}function g(){return[{key:"account",name:"账户:",placeholder:"请输入要筛选的账号"},{key:"begin:end",name:"开始结束时间:",type:a.DateRange,span:12,placeholder:"请选择开始时间:请选择结束时间",defaultValue:[s().toISOString(),new Date().toISOString()]}]}const I=d({name:"LoginStats",setup(){const{logins:e}=r(),i=async t=>l({limit:t.limit,offset:t.offset,account:t.account||"",begin:t.begin||"",end:t.end||"",order:"-updatedAt"});return u(()=>{n()}),{logins:e,fetchLogins:i}},render(){const{logins:e,fetchLogins:i}=this;return c(o,{title:"登录查询",filters:g(),columns:p(),data:e,fetch:i},null)}});export{I as default}; 2 | -------------------------------------------------------------------------------- /asset/dist/assets/DNSResult-8c28b47b.js: -------------------------------------------------------------------------------- 1 | import{u as i,A as u}from"./detector-090ed643.js";import{d as l,c as o}from"./ExTable-dcbd254b.js";import{N as c}from"./index-aeeb7070.js";import{E as m,n as d}from"./ExDetectorResultTable-2191f62a.js";import{d as p,N as e}from"./ui-32540f7e.js";import{M as f,C as k,r as y}from"./naive-094f875f.js";import"./ExFormInterface-d830d527.js";import"./common-a7d1cc73.js";const D="p1iljsez",A=p({name:"DNSResult",setup(){const t=async s=>{await u(s)};return{dnsDetectorResults:i().dnsDetectorResults,fetch:t}},render(){const{dnsDetectorResults:t,fetch:s}=this,a=[{title:"名称",key:"taskName"},l({title:"结果",key:"result.desc"}),{title:"域名",key:"host"},{title:"最大耗时(ms)",key:"maxDuration"},o({title:"失败信息",key:"messages"}),{title:"更新于",key:"updatedAt",render(r){return c(r.updatedAt)}},{title:"更多",key:"",render(r){const n=[{title:"DNS服务器",key:"server",fixed:"left",width:200},l({title:"结果",key:"result.desc"}),o({title:"IP列表",key:"ips"}),{title:"耗时(ms)",key:"duration"},{title:"失败信息",key:"message"}];return e(k,{placement:"left-end",trigger:"click"},{default:()=>[e("div",{class:D},[e(f,{columns:n,data:r.results},null)])],...{trigger:d}})}}];return e(y,{title:"DNS检测结果"},{default:()=>[e(m,{columns:a,data:t,fetch:s,category:"dns"},null)]})}});export{A as default}; 2 | -------------------------------------------------------------------------------- /middleware/ip_blocker.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/vicanso/elton" 21 | "github.com/vicanso/hes" 22 | ) 23 | 24 | var ( 25 | ErrIPNotAllow = &hes.Error{ 26 | StatusCode: http.StatusBadRequest, 27 | Message: "request is forbidden", 28 | Category: "IB", 29 | } 30 | ) 31 | 32 | type IPBlockFunc func(string) bool 33 | 34 | // NewIPBlocker create a new block ip middleware 35 | func NewIPBlocker(fn IPBlockFunc) elton.Handler { 36 | return func(c *elton.Context) error { 37 | if fn(c.RealIP()) { 38 | return ErrIPNotAllow 39 | } 40 | return c.Next() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /middleware/url_prefix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "fmt" 19 | "net/http" 20 | "regexp" 21 | "strings" 22 | 23 | "github.com/vicanso/elton" 24 | ) 25 | 26 | // NewPrefixHandler create a new request url prefix handler 27 | func NewPrefixHandler(prefixes []string) elton.PreHandler { 28 | var prefixReg *regexp.Regexp 29 | if len(prefixes) != 0 { 30 | prefixReg = regexp.MustCompile(fmt.Sprintf(`^(%s)`, strings.Join(prefixes, "|"))) 31 | } 32 | return func(req *http.Request) { 33 | if prefixReg == nil { 34 | return 35 | } 36 | req.URL.Path = prefixReg.ReplaceAllString(req.URL.Path, "") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /asset/dist/assets/HTTPServerInterceptors-873741cf.js: -------------------------------------------------------------------------------- 1 | import{D as m,s as n,E as i}from"./index-aeeb7070.js";import{E as p}from"./ExLoading-572e76eb.js";import{F as o}from"./ExFormInterface-d830d527.js";import{E as c}from"./ExConfigEditorList-10d35f0e.js";import{C as l}from"./configs-5a9029c7.js";import{d as u,f as d,N as s}from"./ui-32540f7e.js";import{u as T}from"./naive-094f875f.js";import"./common-a7d1cc73.js";import"./ExConfigEditor-da704c67.js";import"./ExForm-cdb0faae.js";import"./detector-090ed643.js";import"./ExConfigTable-74c02379.js";import"./ExTable-dcbd254b.js";const k=u({name:"HTTPServerInterceptors",setup(){const e=T(),{routers:t}=i();return d(async()=>{try{await m()}catch(r){n(e,r)}}),{routers:t}},render(){const{routers:e}=this;if(e.processing)return s(p,null,null);const t=[{name:"路由:",key:"data.router",type:o.Select,placeholder:"请选择路由",options:e.items.map(r=>{const a=`${r.method} ${r.route}`;return{label:a,value:a}})},{name:"前置脚本:",key:"data.before",type:o.TextArea,span:24,placeholder:"请输入请求处理前的相关处理脚本"},{name:"后置脚本:",key:"data.after",type:o.TextArea,span:24,placeholder:"请输入请求处理后的相关处理脚本"}];return s(c,{listTitle:"HTTP服务拦截配置",editorTitle:"添加/更新HTTP服务拦截配置",editorDescription:"设置HTTP服务各路由的拦截配置",category:l.HTTPServerInterceptor,extraFormItems:t},null)}});export{k as default}; 2 | -------------------------------------------------------------------------------- /location/location_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package location 16 | 17 | import ( 18 | "context" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/vicanso/go-axios" 23 | ) 24 | 25 | func TestGetLocationByIP(t *testing.T) { 26 | assert := assert.New(t) 27 | 28 | done := ins.Mock(&axios.Response{ 29 | Status: 200, 30 | Data: []byte(`{ 31 | "ip": "47.242.95.102", 32 | "country": "美国", 33 | "province": "加利福尼亚", 34 | "city": "圣克拉拉", 35 | "isp": "阿里巴巴" 36 | }`), 37 | }) 38 | defer done() 39 | lo, err := GetByIP(context.Background(), "127.0.0.1") 40 | assert.Nil(err) 41 | assert.Equal("圣克拉拉", lo.City) 42 | } 43 | -------------------------------------------------------------------------------- /log/log_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package log 16 | 17 | import ( 18 | "net/url" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | ) 23 | 24 | func TestLogStruct(t *testing.T) { 25 | assert := assert.New(t) 26 | 27 | type T struct { 28 | Name string 29 | } 30 | 31 | e := Struct(&T{ 32 | Name: "abc", 33 | }) 34 | assert.NotNil(e) 35 | 36 | e = Struct([]*T{ 37 | { 38 | Name: "abc", 39 | }, { 40 | Name: "def", 41 | }, 42 | }) 43 | assert.NotNil(e) 44 | 45 | e = Struct(url.Values{ 46 | "key": []string{ 47 | "a", 48 | "b", 49 | }, 50 | "name": []string{ 51 | "a", 52 | }, 53 | }) 54 | assert.NotNil(e) 55 | } 56 | -------------------------------------------------------------------------------- /service/application_test.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestApplicationStatus(t *testing.T) { 10 | assert := assert.New(t) 11 | originalApplicationStatus := applicationStatus.Load() 12 | defer SetApplicationStatus(originalApplicationStatus) 13 | 14 | assert.Equal(originalApplicationStatus, GetApplicationStatus()) 15 | 16 | SetApplicationStatus(ApplicationStatusStopped) 17 | assert.False(ApplicationIsRunning()) 18 | SetApplicationStatus(ApplicationStatusStopping) 19 | assert.False(ApplicationIsRunning()) 20 | SetApplicationStatus(ApplicationStatusRunning) 21 | assert.True(ApplicationIsRunning()) 22 | } 23 | 24 | func TestApplicationVersion(t *testing.T) { 25 | assert := assert.New(t) 26 | originalVersion := applicationVersion 27 | defer SetApplicationVersion(originalVersion) 28 | 29 | v := "2020" 30 | SetApplicationVersion(v) 31 | assert.Equal(v, GetApplicationVersion()) 32 | } 33 | 34 | func TestApplicationBuildedAt(t *testing.T) { 35 | assert := assert.New(t) 36 | originalBuildedAt := applicationBuildedAt 37 | defer SetApplicationBuildedAt(originalBuildedAt) 38 | 39 | buildedAt := "2020" 40 | SetApplicationBuildedAt(buildedAt) 41 | assert.Equal(buildedAt, GetApplicationBuildedAt()) 42 | } 43 | -------------------------------------------------------------------------------- /asset/dist/assets/SessionInterceptors-01600b82.js: -------------------------------------------------------------------------------- 1 | import{E as n}from"./ExConfigEditorList-10d35f0e.js";import{F as s}from"./ExFormInterface-d830d527.js";import{E as i}from"./ExLoading-572e76eb.js";import{D as m,s as p,E as l}from"./index-aeeb7070.js";import{C as c}from"./configs-5a9029c7.js";import{d as u,f as d,N as a}from"./ui-32540f7e.js";import{u as f}from"./naive-094f875f.js";import"./ExConfigEditor-da704c67.js";import"./ExForm-cdb0faae.js";import"./detector-090ed643.js";import"./ExConfigTable-74c02379.js";import"./ExTable-dcbd254b.js";import"./common-a7d1cc73.js";const T=u({name:"SessionInterceptors",setup(){const t=f(),{routers:e}=l();return d(async()=>{try{await m()}catch(r){p(t,r)}}),{routers:e}},render(){const{routers:t}=this;if(t.processing)return a(i,null,null);const e=[];t.items.forEach(o=>{e.includes(o.route)||e.push(o.route)}),e.sort();const r=[{type:s.Blank,name:"",key:""},{name:"提示信息:",key:"data.message",placeholder:"请输入出错提示信息"},{name:"允许账号:",key:"data.allowAccount",placeholder:"请输入允许账号,多个账号以,分割"},{name:"允许路由:",key:"data.allowRoutes",type:s.MultiSelect,placeholder:"请输入允许路由,可以多选",options:e.map(o=>({label:o,value:o}))}];return a(n,{listTitle:"Session拦截配置",editorTitle:"添加/更新Session拦截配置",editorDescription:"设置Session拦截的相关配置",category:c.SessionInterceptor,extraFormItems:r},null)}});export{T as default}; 2 | -------------------------------------------------------------------------------- /util/map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package util 16 | 17 | // MergeMapStringInterface 合并map[string]interface{} 18 | func MergeMapStringInterface(target map[string]interface{}, sources ...map[string]interface{}) map[string]interface{} { 19 | for _, source := range sources { 20 | for key, value := range source { 21 | if value != nil { 22 | target[key] = value 23 | } 24 | } 25 | } 26 | return target 27 | } 28 | 29 | func MergeMapString(target map[string]string, sources ...map[string]string) map[string]string { 30 | for _, source := range sources { 31 | for key, value := range source { 32 | if value != "" { 33 | target[key] = value 34 | } 35 | } 36 | } 37 | return target 38 | } 39 | -------------------------------------------------------------------------------- /asset/dist/assets/configs-5a9029c7.js: -------------------------------------------------------------------------------- 1 | import{x as c,J as s,K as a,L as r}from"./index-aeeb7070.js";import{j as o,b as u}from"./ui-32540f7e.js";var l=(n=>(n.MockTime="mockTime",n.BlockIP="blockIP",n.SignedKey="signedKey",n.RouterConcurrency="routerConcurrency",n.SessionInterceptor="sessionInterceptor",n.RequestConcurrency="requestConcurrency",n.Router="router",n.Email="email",n.HTTPServerInterceptor="httpServerInterceptor",n))(l||{}),f=(n=>(n[n.Enabled=1]="Enabled",n[n.Disabled=2]="Disabled",n))(f||{});const e=o({processing:!1,items:[],count:-1});function d(n){n.key=`${n.id}`}async function y(n){const{data:t}=await c.post(s,n);return t}async function k(){const{data:n}=await c.get(s,{params:{category:"mockTime",name:"mockTime",limit:1}}),t=n.configurations||[];return t.length===0?{}:t[0]}async function T(n){const t=r.replace(":id",`${n}`),{data:i}=await c.get(t);return i}async function b(n){const t=r.replace(":id",`${n.id}`);await c.patch(t,n.data)}async function g(n){if(!e.processing){n.limit||(n.limit=50);try{e.processing=!0;const{data:t}=await c.get(s,{params:n}),i=t.count||0;i>=0&&(e.count=i),e.items=t.configurations||[],e.items.forEach(d)}finally{e.processing=!1}}}function w(){e.items=[],e.count=-1}async function D(){const{data:n}=await c.get(a);return n}const m={configs:u(e)};function h(){return m}export{l as C,D as a,f as b,k as c,b as d,y as e,T as f,w as g,g as h,h as u}; 2 | -------------------------------------------------------------------------------- /session/session.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "github.com/vicanso/elton" 19 | session "github.com/vicanso/elton-session" 20 | "github.com/vicanso/cybertect/cache" 21 | "github.com/vicanso/cybertect/config" 22 | "github.com/vicanso/cybertect/util" 23 | ) 24 | 25 | var scf = config.MustGetSessionConfig() 26 | 27 | // New new session middleware 28 | func New() elton.Handler { 29 | store := cache.GetRedisSession() 30 | return session.NewByCookie(session.CookieConfig{ 31 | Store: store, 32 | Signed: true, 33 | Expired: scf.TTL, 34 | GenID: func() string { 35 | return util.GenXID() 36 | }, 37 | Name: scf.Key, 38 | Path: scf.CookiePath, 39 | MaxAge: int(scf.TTL.Seconds()), 40 | HttpOnly: true, 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /schema/databasedetector.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/ent" 19 | "entgo.io/ent/schema/field" 20 | ) 21 | 22 | type DatabaseDetector struct { 23 | ent.Schema 24 | } 25 | 26 | // Fields of the DatabaseDetector 27 | func (DatabaseDetector) Fields() []ent.Field { 28 | // TODO 是否要增加证书配置 29 | return []ent.Field{ 30 | field.Strings("uris"). 31 | Comment("redis连接串列表"), 32 | field.Text("certPem"). 33 | Optional(). 34 | Comment("cert pem block数据"), 35 | field.Text("keyPem"). 36 | Optional(). 37 | Comment("key pem block数据"), 38 | } 39 | } 40 | 41 | // Mixin of the DatabaseDetector 42 | func (DatabaseDetector) Mixin() []ent.Mixin { 43 | return []ent.Mixin{ 44 | TimeMixin{}, 45 | StatusMixin{}, 46 | DetectorMixin{}, 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /web/src/components/ExDetectorTable.tsx: -------------------------------------------------------------------------------- 1 | import { TableColumn } from "naive-ui/lib/data-table/src/interface"; 2 | import { newListColumn } from "../components/ExTable"; 3 | import { formatDate } from "../helpers/util"; 4 | 5 | export function getDefaultColumns(): TableColumn[] { 6 | return [ 7 | { 8 | title: "名称", 9 | key: "name", 10 | width: 120, 11 | fixed: "left", 12 | }, 13 | { 14 | title: "状态", 15 | key: "status", 16 | render(row: Record) { 17 | return row.statusDesc as string; 18 | }, 19 | width: 60, 20 | }, 21 | { 22 | title: "超时设置", 23 | key: "timeout", 24 | width: 80, 25 | }, 26 | { 27 | title: "检测间隔", 28 | key: "interval", 29 | width: 80, 30 | }, 31 | newListColumn({ 32 | key: "owners", 33 | title: "所有人", 34 | width: 120, 35 | }), 36 | newListColumn({ 37 | key: "receivers", 38 | title: "告警接收人", 39 | width: 120, 40 | }), 41 | { 42 | title: "更新于", 43 | key: "updatedAt", 44 | width: 180, 45 | render(row: Record) { 46 | return formatDate(row.updatedAt as string); 47 | }, 48 | }, 49 | { 50 | title: "配置描述", 51 | key: "description", 52 | width: 100, 53 | ellipsis: { 54 | tooltip: true, 55 | }, 56 | }, 57 | ]; 58 | } 59 | -------------------------------------------------------------------------------- /validate/user.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package validate 16 | 17 | func init() { 18 | // 用户账号 19 | AddAlias("xUserAccount", "ascii,min=2,max=10") 20 | // 用户密码 21 | AddAlias("xUserPassword", "ascii,len=44") 22 | // 用户名称 23 | AddAlias("xUserName", "min=1,max=20") 24 | // 用户邮箱 25 | AddAlias("xUserEmail", "email") 26 | // 用户角色 27 | AddAlias("xUserRole", "ascii,min=1,max=10") 28 | // 用户分组 29 | AddAlias("xUserGroup", "ascii,min=1,max=10") 30 | // 用户接收告警地址 31 | // 因为更新时使用空格表示删,因此不限制为url 32 | AddAlias("xUserAlarmURL", "ascii,min=1,max=200") 33 | // 用户行为分类 34 | // TODO 是否调整为支持配置的方式 35 | Add("xUserActionCategory", newIsInString([]string{ 36 | "click", 37 | "login", 38 | "register", 39 | "routeChange", 40 | "error", 41 | })) 42 | // 用户行为触发所在路由 43 | AddAlias("xUserActionRoute", "max=50") 44 | } 45 | -------------------------------------------------------------------------------- /asset/dist/assets/Caches-62ddc5f6.js: -------------------------------------------------------------------------------- 1 | import{H as p,x as h,a as d,s as f,t as v}from"./index-aeeb7070.js";import{d as m,r as u,N as s,c}from"./ui-32540f7e.js";import{u as C,v as N,w as n,y as w,d as y,r as k}from"./naive-094f875f.js";import"./common-a7d1cc73.js";async function g(e){const a=p.replace(":key",e),{data:t}=await h.get(a);return t}async function F(e){const a=p.replace(":key",e);await h.delete(a)}const z=m({name:"CachesView",setup(){const e=C(),a=u(""),t=u(!1),l=u("");return{processing:t,key:a,fetch:async()=>{if(!a.value){d(e,"请输入要查询的key");return}if(!t.value){t.value=!0;try{l.value="";const r=await g(a.value);try{const i=JSON.parse(r.data);l.value=JSON.stringify(i,null,2)}catch{l.value=r.data}}catch(r){f(e,r)}finally{t.value=!1}}},del:async()=>{if(!a.value){d(e,"请输入要删除的key");return}if(!t.value)try{l.value="",await F(a.value),v(e,"已成功清除数据")}catch(r){f(e,r)}finally{t.value=!1}},cacheData:l}},render(){const e="large",{fetch:a,cacheData:t,del:l}=this;return s(k,{title:"缓存查询与清除"},{default:()=>[s("p",null,[c("session的缓存格式 ss:sessionID")]),s(N,{xGap:24},{default:()=>[s(n,{span:12},{default:()=>[s(w,{placeholder:"请输入缓存的key",size:e,clearable:!0,onUpdateValue:o=>{this.key=o}},null)]}),s(n,{span:6},{default:()=>[s(y,{class:"widthFull",size:e,onClick:()=>a()},{default:()=>[c("查询")]})]}),s(n,{span:6},{default:()=>[s(y,{class:"widthFull",size:e,onClick:()=>l()},{default:()=>[c("清除")]})]}),t&&s(n,{span:24},{default:()=>[s("pre",null,[t])]})]})]})}});export{z as default}; 2 | -------------------------------------------------------------------------------- /middleware/ip_blocker_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "net/http/httptest" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/vicanso/elton" 23 | ) 24 | 25 | func TestNewIPBlocker(t *testing.T) { 26 | assert := assert.New(t) 27 | blockFn := func(ip string) bool { 28 | return ip == "1.1.1.1" 29 | } 30 | fn := NewIPBlocker(blockFn) 31 | 32 | req := httptest.NewRequest("GET", "/", nil) 33 | req.Header.Set(elton.HeaderXForwardedFor, "1.1.1.1") 34 | c := elton.NewContext(nil, req) 35 | err := fn(c) 36 | assert.Equal(ErrIPNotAllow, err) 37 | 38 | req.Header.Del(elton.HeaderXForwardedFor) 39 | // 由于context的ip会缓存,因此重新创建 40 | c = elton.NewContext(nil, req) 41 | done := false 42 | c.Next = func() error { 43 | done = true 44 | return nil 45 | } 46 | err = fn(c) 47 | assert.Nil(err) 48 | assert.True(done) 49 | } 50 | -------------------------------------------------------------------------------- /middleware/entry_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "net/http/httptest" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/vicanso/elton" 23 | ) 24 | 25 | func TestNewEntry(t *testing.T) { 26 | assert := assert.New(t) 27 | req := httptest.NewRequest("GET", "/", nil) 28 | c := elton.NewContext(httptest.NewRecorder(), req) 29 | c.ID = "abc" 30 | done := false 31 | c.Next = func() error { 32 | done = true 33 | return nil 34 | } 35 | doneEntry := false 36 | doneExit := false 37 | 38 | fn := NewEntry(func() int32 { 39 | doneEntry = true 40 | return 0 41 | }, func() int32 { 42 | doneExit = true 43 | return 0 44 | }) 45 | err := fn(c) 46 | assert.Nil(err) 47 | assert.True(done) 48 | assert.True(doneEntry) 49 | assert.True(doneExit) 50 | assert.Equal("abc", c.GetHeader(xResponseID)) 51 | assert.Equal("no-cache", c.GetHeader("Cache-Control")) 52 | } 53 | -------------------------------------------------------------------------------- /web/src/components/ExFluxDetail.tsx: -------------------------------------------------------------------------------- 1 | import { InfoCircle } from "@vicons/fa"; 2 | import { NIcon, NPopover } from "naive-ui"; 3 | import { defineComponent, PropType } from "vue"; 4 | import ExFluxDetailList from "./ExFluxDetailList"; 5 | 6 | export default defineComponent({ 7 | name: "ExFluxDetail", 8 | props: { 9 | measurement: { 10 | type: String, 11 | required: true, 12 | }, 13 | data: { 14 | type: Object as PropType>, 15 | required: true, 16 | }, 17 | tagKeys: { 18 | type: Array as PropType, 19 | required: true, 20 | }, 21 | }, 22 | render() { 23 | const { data, measurement, tagKeys } = this.$props; 24 | const slots = { 25 | trigger: () => ( 26 | 27 | 28 | 29 | ), 30 | }; 31 | const tags: Record = {}; 32 | Object.keys(data).forEach((key) => { 33 | if (!tagKeys.includes(key)) { 34 | return; 35 | } 36 | const v = data[key]; 37 | if (!v) { 38 | return; 39 | } 40 | tags[key] = v as string; 41 | }); 42 | return ( 43 | 50 | 55 | 56 | ); 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /schema/databasedetectorresult.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "entgo.io/ent" 19 | "entgo.io/ent/schema/field" 20 | ) 21 | 22 | type DatabaseDetectorResult struct { 23 | ent.Schema 24 | } 25 | 26 | type DatabaseDetectorSubResult struct { 27 | Result DetectorResult `json:"result"` 28 | URI string `json:"uri"` 29 | Duration int `json:"duration"` 30 | Message string `json:"message"` 31 | } 32 | 33 | type DatabaseDetectorSubResults []*DatabaseDetectorSubResult 34 | 35 | // Fields of the DatabaseDetectorResult 36 | func (DatabaseDetectorResult) Fields() []ent.Field { 37 | return []ent.Field{ 38 | field.Strings("uris"). 39 | Comment("检测的redis连接地址"), 40 | field.JSON("results", DatabaseDetectorSubResults{}). 41 | Comment("检测结果列表"), 42 | } 43 | } 44 | 45 | func (DatabaseDetectorResult) Mixin() []ent.Mixin { 46 | return []ent.Mixin{ 47 | TimeMixin{}, 48 | DetectorResultMixin{}, 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.air.toml: -------------------------------------------------------------------------------- 1 | # Config file for [Air](https://github.com/cosmtrek/air) in TOML format 2 | 3 | # Working directory 4 | # . or absolute path, please note that the directories following must be under root. 5 | root = "." 6 | tmp_dir = "tmp" 7 | 8 | [build] 9 | # Just plain old shell command. You could use `make` as well. 10 | cmd = "go build -o ./tmp/main ." 11 | # Binary file yields from `cmd`. 12 | bin = "tmp/main" 13 | # Customize binary. 14 | full_bin = "GO_ENV=dev MAIL_USER=test@outlook.com SECRET=cybertect ./tmp/main" 15 | # Watch these filename extensions. 16 | include_ext = ["go", "yml"] 17 | # Ignore these filename extensions or directories. 18 | exclude_dir = ["assets", "tmp", "vendor", "web"] 19 | # Watch these directories if you specified. 20 | include_dir = [] 21 | # Exclude files. 22 | exclude_file = [] 23 | # Exclude unchanged files. 24 | exclude_unchanged = true 25 | # This log file places in your tmp_dir. 26 | log = "air.log" 27 | # It's not necessary to trigger build each time file changes if it's too frequent. 28 | delay = 1000 # ms 29 | # Stop running old binary when build errors occur. 30 | stop_on_error = true 31 | # Send Interrupt signal before killing process (windows does not support this feature) 32 | send_interrupt = true 33 | # Delay after sending Interrupt signal 34 | kill_delay = 500 # ms 35 | 36 | [log] 37 | # Show log time 38 | time = false 39 | 40 | [color] 41 | # Customize each part's color. If no color found, use the raw app log. 42 | main = "magenta" 43 | watcher = "cyan" 44 | build = "yellow" 45 | runner = "green" 46 | 47 | [misc] 48 | # Delete tmp directory on exit 49 | clean_on_exit = true -------------------------------------------------------------------------------- /asset/dist/assets/RouterMocks-02b867d7.js: -------------------------------------------------------------------------------- 1 | import{E as n}from"./ExConfigEditorList-10d35f0e.js";import{C as m}from"./configs-5a9029c7.js";import{F as e}from"./ExFormInterface-d830d527.js";import{D as l,s as p,E as i}from"./index-aeeb7070.js";import{E as c}from"./ExLoading-572e76eb.js";import{d as u,f as d,N as s}from"./ui-32540f7e.js";import{u as y}from"./naive-094f875f.js";import"./ExConfigEditor-da704c67.js";import"./ExForm-cdb0faae.js";import"./detector-090ed643.js";import"./ExConfigTable-74c02379.js";import"./ExTable-dcbd254b.js";import"./common-a7d1cc73.js";const v=u({name:"RouterMocks",setup(){const{routers:t}=i(),r=y();return d(async()=>{try{await l()}catch(o){p(r,o)}}),{routers:t}},render(){const{routers:t}=this;if(t.processing)return s(c,null,null);const r=[{type:e.Blank,name:"",key:""},{name:"路由:",key:"data.router",type:e.Select,placeholder:"请选择路由",options:t.items.map(o=>{const a=`${o.method} ${o.route}`;return{label:a,value:a}})},{name:"状态码:",key:"data.status",type:e.InputNumber,placeholder:"请输入响应状态码"},{name:"响应数据类型:",type:e.Select,key:"data.contentType",placeholder:"请选择响应数据类型",options:[{label:"json",value:"application/json; charset=UTF-8"},{label:"plain",value:"text/plain; charset=UTF-8"},{label:"html",value:"text/html; charset=UTF-8"}]},{name:"延时响应:",key:"data.delaySeconds",type:e.InputNumber,placeholder:"请输入延时响应时长(秒)"},{name:"完整URL:",key:"data.url",placeholder:"请输入完整的url(可选)"},{name:"响应数据:",key:"data.response",type:e.TextArea,span:24,placeholder:"请输入响应数据"}];return s(n,{listTitle:"路由Mock配置",editorTitle:"添加/更新路由Mock配置",editorDescription:"设置各路由的Mock响应",category:m.Router,extraFormItems:r},null)}});export{v as default}; 2 | -------------------------------------------------------------------------------- /middleware/interceptor.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package middleware 16 | 17 | import ( 18 | "github.com/vicanso/elton" 19 | "github.com/vicanso/cybertect/interceptor" 20 | ) 21 | 22 | func NewInterceptor() elton.Handler { 23 | return func(c *elton.Context) error { 24 | inter, err := interceptor.NewHTTPServer(c) 25 | if err != nil { 26 | return err 27 | } 28 | // 如果返回空表示没有设置interceptor 29 | if inter == nil { 30 | return c.Next() 31 | } 32 | // 前置处理 33 | resp, err := inter.Before() 34 | if err != nil { 35 | return err 36 | } 37 | // 如果状态码不为0,则表示已设置响应数据 38 | if resp != nil && resp.Status != 0 { 39 | resp.SetResponse(c) 40 | return nil 41 | } 42 | err = c.Next() 43 | if err != nil { 44 | return err 45 | } 46 | // 后置处理 47 | resp, err = inter.After() 48 | if err != nil { 49 | return err 50 | } 51 | // 如果状态码不为0,则表示重设响应数据 52 | if resp != nil && resp.Status != 0 { 53 | resp.SetResponse(c) 54 | return nil 55 | } 56 | return nil 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /util/context_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package util 15 | 16 | import ( 17 | "net/http" 18 | "net/http/httptest" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/assert" 22 | "github.com/vicanso/elton" 23 | "github.com/vicanso/cybertect/config" 24 | ) 25 | 26 | func TestGetTrackID(t *testing.T) { 27 | assert := assert.New(t) 28 | sessionConfig = config.MustGetSessionConfig() 29 | req := httptest.NewRequest("GET", "/", nil) 30 | cookie := http.Cookie{ 31 | Name: sessionConfig.TrackKey, 32 | Value: "abcd", 33 | } 34 | req.AddCookie(&cookie) 35 | c := elton.NewContext(nil, req) 36 | assert.Equal(cookie.Value, GetTrackID(c)) 37 | } 38 | 39 | func TestGetSessionID(t *testing.T) { 40 | assert := assert.New(t) 41 | sessionConfig = config.MustGetSessionConfig() 42 | req := httptest.NewRequest("GET", "/", nil) 43 | cookie := http.Cookie{ 44 | Name: sessionConfig.Key, 45 | Value: "abcd", 46 | } 47 | req.AddCookie(&cookie) 48 | c := elton.NewContext(nil, req) 49 | assert.Equal(cookie.Value, GetSessionID(c)) 50 | } 51 | -------------------------------------------------------------------------------- /schema/time.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 tree xie 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package schema 16 | 17 | import ( 18 | "time" 19 | 20 | "entgo.io/ent" 21 | "entgo.io/ent/schema/field" 22 | "entgo.io/ent/schema/index" 23 | "entgo.io/ent/schema/mixin" 24 | ) 25 | 26 | // TimeMixin 公共的时间schema 27 | type TimeMixin struct { 28 | mixin.Schema 29 | } 30 | 31 | // Fields 公共时间schema的字段,包括创建于与更新于 32 | func (TimeMixin) Fields() []ent.Field { 33 | return []ent.Field{ 34 | field.Time("created_at"). 35 | // 对于多个单词组成的,如果需要使用select,则需要添加sql tag 36 | StructTag(`json:"createdAt" sql:"created_at"`). 37 | Immutable(). 38 | Default(time.Now). 39 | Comment("创建时间,添加记录时由程序自动生成"), 40 | field.Time("updated_at"). 41 | StructTag(`json:"updatedAt" sql:"updated_at"`). 42 | Default(time.Now). 43 | Immutable(). 44 | UpdateDefault(time.Now). 45 | Comment("更新时间,更新记录时由程序自动生成"), 46 | } 47 | } 48 | 49 | // Indexes 公共时间字段索引 50 | func (TimeMixin) Indexes() []ent.Index { 51 | return []ent.Index{ 52 | index.Fields("created_at"), 53 | index.Fields("updated_at"), 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + Typescript + Vite 2 | 3 | This template should help get you started developing with Vue 3 and Typescript in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur). Make sure to enable `vetur.experimental.templateInterpolationService` in settings! 8 | 9 | ### If Using `