├── public ├── minio │ └── test.txt ├── rsa.go ├── alioss │ └── alioss.go ├── params.go └── printlogo.go ├── front ├── .husky │ ├── .gitignore │ ├── pre-commit │ └── commit-msg ├── .browserslistrc ├── .env ├── .env.preview ├── .env.development ├── .eslintrc.json ├── src │ ├── components │ │ ├── MultiTab │ │ │ ├── events.js │ │ │ ├── index.less │ │ │ └── index.js │ │ ├── Trend │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── Trend.vue │ │ │ └── index.md │ │ ├── Ellipsis │ │ │ ├── index.js │ │ │ ├── index.md │ │ │ └── Ellipsis.vue │ │ ├── NoticeIcon │ │ │ └── index.js │ │ ├── NumberInfo │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── index.md │ │ │ └── NumberInfo.vue │ │ ├── IconSelector │ │ │ ├── index.js │ │ │ └── README.md │ │ ├── SettingDrawer │ │ │ ├── index.js │ │ │ ├── SettingItem.vue │ │ │ ├── themeColor.js │ │ │ └── settingConfig.js │ │ ├── StandardFormRow │ │ │ └── index.js │ │ ├── ArticleListContent │ │ │ └── index.js │ │ ├── FooterToolbar │ │ │ ├── index.js │ │ │ ├── index.less │ │ │ ├── FooterToolBar.vue │ │ │ └── index.md │ │ ├── AvatarList │ │ │ ├── index.js │ │ │ ├── Item.jsx │ │ │ └── index.less │ │ ├── index.less │ │ ├── TextArea │ │ │ └── style.less │ │ ├── Charts │ │ │ ├── chart.less │ │ │ ├── smooth.area.less │ │ │ ├── MiniArea.vue │ │ │ ├── MiniBar.vue │ │ │ ├── ClusterFinishNumChart.vue │ │ │ ├── TransferBar.vue │ │ │ ├── ClusterTaskBar.vue │ │ │ ├── ClusterFinishBar.vue │ │ │ ├── MiniSmoothArea.vue │ │ │ ├── Liquid.vue │ │ │ ├── Radar.vue │ │ │ └── ClusterTaskNumChart.vue │ │ ├── Search │ │ │ └── index.less │ │ ├── SelectLang │ │ │ ├── index.less │ │ │ └── index.jsx │ │ ├── GlobalFooter │ │ │ └── index.vue │ │ ├── _util │ │ │ └── util.js │ │ ├── TagSelect │ │ │ └── TagSelectOption.jsx │ │ ├── Editor │ │ │ └── WangEditor.vue │ │ └── GlobalHeader │ │ │ └── RightContent.vue │ ├── assets │ │ ├── overview.png │ │ └── logo.svg │ ├── locales │ │ ├── lang │ │ │ ├── en-US │ │ │ │ ├── form.js │ │ │ │ ├── account.js │ │ │ │ ├── dashboard.js │ │ │ │ ├── result.js │ │ │ │ ├── global.js │ │ │ │ ├── result │ │ │ │ │ ├── fail.js │ │ │ │ │ └── success.js │ │ │ │ └── setting.js │ │ │ ├── zh-CN │ │ │ │ ├── form.js │ │ │ │ ├── account.js │ │ │ │ ├── dashboard.js │ │ │ │ ├── global.js │ │ │ │ ├── result.js │ │ │ │ ├── result │ │ │ │ │ ├── fail.js │ │ │ │ │ └── success.js │ │ │ │ ├── setting.js │ │ │ │ ├── menu.js │ │ │ │ └── dashboard │ │ │ │ │ └── analysis.js │ │ │ ├── zh-CN.js │ │ │ └── en-US.js │ │ └── index.js │ ├── views │ │ ├── account │ │ │ ├── center │ │ │ │ └── page │ │ │ │ │ └── index.js │ │ │ └── settings │ │ │ │ ├── Binding.vue │ │ │ │ └── Notification.vue │ │ ├── 404.vue │ │ ├── dashboard │ │ │ └── Monitor.vue │ │ ├── list │ │ │ ├── search │ │ │ │ └── components │ │ │ │ │ ├── IconText.vue │ │ │ │ │ └── CardInfo.vue │ │ │ ├── QueryList.vue │ │ │ └── components │ │ │ │ └── Info.vue │ │ ├── exception │ │ │ ├── 403.vue │ │ │ ├── 404.vue │ │ │ └── 500.vue │ │ ├── user │ │ │ └── RegisterResult.vue │ │ ├── settings │ │ │ └── IconSelectorView.vue │ │ ├── cluster │ │ │ ├── task │ │ │ │ ├── model.vue │ │ │ │ ├── elastic │ │ │ │ │ └── ClusterElasticTask.vue │ │ │ │ └── mysql │ │ │ │ │ └── ClusterMysqlTask.vue │ │ │ ├── service │ │ │ │ └── components │ │ │ │ │ └── Info.vue │ │ │ └── history │ │ │ │ ├── mysql │ │ │ │ └── components │ │ │ │ │ └── Info.vue │ │ │ │ ├── model.vue │ │ │ │ └── elastic │ │ │ │ └── components │ │ │ │ └── Info.vue │ │ ├── local │ │ │ ├── app │ │ │ │ └── components │ │ │ │ │ └── Info.vue │ │ │ ├── history │ │ │ │ └── mysql │ │ │ │ │ └── components │ │ │ │ │ └── Info.vue │ │ │ └── task │ │ │ │ └── taskList.vue │ │ ├── result │ │ │ └── Error.vue │ │ └── form │ │ │ └── stepForm │ │ │ └── StepForm.vue │ ├── layouts │ │ ├── PageView.vue │ │ ├── BlankLayout.vue │ │ ├── index.js │ │ ├── RouteView.vue │ │ └── BasicLayout.less │ ├── store │ │ ├── device-mixin.js │ │ ├── i18n-mixin.js │ │ ├── getters.js │ │ ├── index.js │ │ ├── mutation-types.js │ │ ├── modules │ │ │ └── async-router.js │ │ └── app-mixin.js │ ├── core │ │ ├── icons.js │ │ ├── use.js │ │ ├── directives │ │ │ └── action.js │ │ ├── bootstrap.js │ │ └── permission │ │ │ └── permission.js │ ├── api │ │ ├── public.js │ │ ├── dashboard.js │ │ ├── agent.js │ │ ├── agent-history.js │ │ ├── host.js │ │ ├── task.js │ │ ├── agent-host.js │ │ ├── manage.js │ │ ├── user.js │ │ ├── agent-task_overview.js │ │ └── bak.js │ ├── utils │ │ ├── filter.js │ │ ├── domUtil.js │ │ ├── axios.js │ │ ├── routeConvert.js │ │ ├── utils.less │ │ └── screenLog.js │ ├── App.vue │ ├── mock │ │ ├── index.js │ │ ├── util.js │ │ └── services │ │ │ └── auth.js │ ├── router │ │ └── index.js │ ├── config │ │ └── defaultSettings.js │ └── main.js ├── tests │ └── unit │ │ └── .eslintrc.js ├── .lintstagedrc.json ├── postcss.config.js ├── public │ ├── logo.png │ └── avatar2.jpg ├── deploy │ ├── caddy.conf │ └── nginx.conf ├── .prettierrc ├── .travis.yml ├── Dockerfile ├── jsconfig.json ├── .gitignore ├── .gitattributes ├── .github │ └── ISSUE_TEMPLATE │ │ ├── need-help-issue.md │ │ ├── feature_request.md │ │ └── bug_report.md ├── jest.config.js ├── commitlint.config.js ├── babel.config.js ├── README.md ├── .editorconfig ├── docs │ ├── webpack-bundle-analyzer.md │ └── add-page-loading-animate.md ├── README.zh-CN.md └── LICENSE ├── .gitignore ├── img ├── app.jpg ├── login.jpg ├── task.jpg ├── add_app .jpg ├── add_task.jpg ├── history.jpg ├── add_es_task.jpg ├── dashboard.jpg ├── user_manage.jpg ├── service_list.jpg └── task_overview.jpg ├── docker └── mysqldump ├── staging └── crypt │ ├── crypt.exe │ ├── main.go │ ├── pkg │ ├── utils.go │ ├── decrypt_dir.go │ ├── decrypt.go │ └── encrypt.go │ └── cmd │ ├── root.go │ ├── decrypt.go │ └── encrypt.go ├── yarn.lock ├── core └── job │ └── system.go ├── test ├── testaddr │ └── main.go ├── date │ └── main.go ├── miniotest │ └── main.go ├── main.go └── grpc │ └── main.go ├── agent ├── agentdto │ ├── dashboard.go │ ├── es_bak.go │ ├── bak.go │ └── es_history.go ├── proto │ ├── esbak │ │ └── es_bak.proto │ └── bak │ │ └── bak.proto ├── pkg │ └── trace │ │ └── jaeger.go ├── agentcontroller │ └── dashboard.go ├── agentdao │ └── agent_date_info.go └── agentservice │ └── es_bak.go ├── dao ├── task.go ├── roledao │ ├── action.go │ ├── permission.go │ ├── user_group.go │ ├── role.go │ └── user_info.go ├── job_history.go └── ding.go ├── job └── system │ └── task_sync_test.go ├── server ├── context.go └── server.go ├── Dockerfile ├── main.go ├── kubernetes ├── conf │ ├── mysqlbak-base-conf.yaml │ ├── mysqlbak-web-nginx-default.yaml │ └── mysqlbak-ini.yaml ├── agent │ ├── agent-conf.yaml │ └── agent-deploy.yaml └── gin-mysqlbak-web-deploy.yaml ├── conf ├── config.go ├── dev │ └── base.toml └── config.ini ├── services └── stopAllTask.go ├── dto └── admin_login.go ├── .goreleaser.yaml ├── middleware ├── recovery.go └── jwt.go ├── LICENSE ├── docs └── swagger.yaml └── router └── httpserver.go /public/minio/test.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front/.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /front/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 10 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.sql 3 | .idea 4 | gin_scaffold 5 | 6 | dist/ 7 | -------------------------------------------------------------------------------- /img/app.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/app.jpg -------------------------------------------------------------------------------- /front/.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_PREVIEW=false 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /img/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/login.jpg -------------------------------------------------------------------------------- /img/task.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/task.jpg -------------------------------------------------------------------------------- /docker/mysqldump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/docker/mysqldump -------------------------------------------------------------------------------- /front/.env.preview: -------------------------------------------------------------------------------- 1 | NODE_ENV=production 2 | VUE_APP_PREVIEW=true 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /front/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint -------------------------------------------------------------------------------- /img/add_app .jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/add_app .jpg -------------------------------------------------------------------------------- /img/add_task.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/add_task.jpg -------------------------------------------------------------------------------- /img/history.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/history.jpg -------------------------------------------------------------------------------- /front/.env.development: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | VUE_APP_PREVIEW=true 3 | VUE_APP_API_BASE_URL=/api -------------------------------------------------------------------------------- /front/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "space-before-function-paren": 0 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /front/src/components/MultiTab/events.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | export default new Vue() 3 | -------------------------------------------------------------------------------- /front/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /img/add_es_task.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/add_es_task.jpg -------------------------------------------------------------------------------- /img/dashboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/dashboard.jpg -------------------------------------------------------------------------------- /img/user_manage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/user_manage.jpg -------------------------------------------------------------------------------- /front/.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.js": "eslint --fix", 3 | "*.{css,less}": "stylelint --fix" 4 | } -------------------------------------------------------------------------------- /front/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /front/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/front/public/logo.png -------------------------------------------------------------------------------- /front/src/components/Trend/index.js: -------------------------------------------------------------------------------- 1 | import Trend from './Trend.vue' 2 | 3 | export default Trend 4 | -------------------------------------------------------------------------------- /img/service_list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/service_list.jpg -------------------------------------------------------------------------------- /img/task_overview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/img/task_overview.jpg -------------------------------------------------------------------------------- /front/public/avatar2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/front/public/avatar2.jpg -------------------------------------------------------------------------------- /staging/crypt/crypt.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/staging/crypt/crypt.exe -------------------------------------------------------------------------------- /front/src/components/Ellipsis/index.js: -------------------------------------------------------------------------------- 1 | import Ellipsis from './Ellipsis' 2 | 3 | export default Ellipsis 4 | -------------------------------------------------------------------------------- /front/src/components/NoticeIcon/index.js: -------------------------------------------------------------------------------- 1 | import NoticeIcon from './NoticeIcon' 2 | export default NoticeIcon 3 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /front/.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /front/src/assets/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noovertime7/gin-mysqlbak/HEAD/front/src/assets/overview.png -------------------------------------------------------------------------------- /front/src/components/NumberInfo/index.js: -------------------------------------------------------------------------------- 1 | import NumberInfo from './NumberInfo' 2 | 3 | export default NumberInfo 4 | -------------------------------------------------------------------------------- /front/src/components/IconSelector/index.js: -------------------------------------------------------------------------------- 1 | import IconSelector from './IconSelector' 2 | export default IconSelector 3 | -------------------------------------------------------------------------------- /front/src/components/SettingDrawer/index.js: -------------------------------------------------------------------------------- 1 | import SettingDrawer from './SettingDrawer' 2 | export default SettingDrawer 3 | -------------------------------------------------------------------------------- /core/job/system.go: -------------------------------------------------------------------------------- 1 | package job 2 | 3 | type Jobber interface { 4 | Start() 5 | Stop() 6 | GetErr() ([]string, bool) 7 | } 8 | -------------------------------------------------------------------------------- /front/src/components/StandardFormRow/index.js: -------------------------------------------------------------------------------- 1 | import StandardFormRow from './StandardFormRow' 2 | 3 | export default StandardFormRow 4 | -------------------------------------------------------------------------------- /front/src/locales/lang/en-US/form.js: -------------------------------------------------------------------------------- 1 | import basicForm from './form/basicForm' 2 | 3 | export default { 4 | ...basicForm 5 | } 6 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/form.js: -------------------------------------------------------------------------------- 1 | import basicForm from './form/basicForm' 2 | 3 | export default { 4 | ...basicForm 5 | } 6 | -------------------------------------------------------------------------------- /front/src/locales/lang/en-US/account.js: -------------------------------------------------------------------------------- 1 | import settings from './account/settings' 2 | 3 | export default { 4 | ...settings 5 | } 6 | -------------------------------------------------------------------------------- /front/src/locales/lang/en-US/dashboard.js: -------------------------------------------------------------------------------- 1 | import analysis from './dashboard/analysis' 2 | 3 | export default { 4 | ...analysis 5 | } 6 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/account.js: -------------------------------------------------------------------------------- 1 | import settings from './account/settings' 2 | 3 | export default { 4 | ...settings 5 | } 6 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/dashboard.js: -------------------------------------------------------------------------------- 1 | import analysis from './dashboard/analysis' 2 | 3 | export default { 4 | ...analysis 5 | } 6 | -------------------------------------------------------------------------------- /front/deploy/caddy.conf: -------------------------------------------------------------------------------- 1 | 0.0.0.0:80 { 2 | gzip 3 | root /usr/share/nginx/html 4 | 5 | rewrite { 6 | r .* 7 | to {path} / 8 | } 9 | } -------------------------------------------------------------------------------- /front/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": false, 4 | "singleQuote": true, 5 | "prettier.spaceBeforeFunctionParen": true 6 | } 7 | -------------------------------------------------------------------------------- /front/src/components/ArticleListContent/index.js: -------------------------------------------------------------------------------- 1 | import ArticleListContent from './ArticleListContent' 2 | 3 | export default ArticleListContent 4 | -------------------------------------------------------------------------------- /front/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10.15.0 4 | cache: yarn 5 | script: 6 | - yarn 7 | - yarn run lint --no-fix && yarn run build 8 | -------------------------------------------------------------------------------- /front/src/components/FooterToolbar/index.js: -------------------------------------------------------------------------------- 1 | import FooterToolBar from './FooterToolBar' 2 | import './index.less' 3 | 4 | export default FooterToolBar 5 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/global.js: -------------------------------------------------------------------------------- 1 | export default { 2 | submit: '提交', 3 | save: '保存', 4 | 'submit.ok': '提交成功', 5 | 'save.ok': '保存成功' 6 | } 7 | -------------------------------------------------------------------------------- /staging/crypt/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/noovertime7/gin-mysqlbak/staging/crypt/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /test/testaddr/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/noovertime7/gin-mysqlbak/services" 4 | 5 | func main() { 6 | services.InitBak() 7 | } 8 | -------------------------------------------------------------------------------- /front/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | RUN rm /etc/nginx/conf.d/default.conf 4 | 5 | ADD deploy/nginx.conf /etc/nginx/conf.d/default.conf 6 | COPY dist/ /usr/share/nginx/html/ 7 | -------------------------------------------------------------------------------- /agent/agentdto/dashboard.go: -------------------------------------------------------------------------------- 1 | package agentdto 2 | 3 | type BarChartOutPut struct { 4 | ServiceName []string `json:"service_name"` 5 | TaskNum []int64 `json:"task_num"` 6 | } 7 | -------------------------------------------------------------------------------- /front/src/locales/lang/en-US/result.js: -------------------------------------------------------------------------------- 1 | import success from './result/success' 2 | import fail from './result/fail' 3 | 4 | export default { 5 | ...success, 6 | ...fail 7 | } 8 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/result.js: -------------------------------------------------------------------------------- 1 | import success from './result/success' 2 | import fail from './result/fail' 3 | 4 | export default { 5 | ...success, 6 | ...fail 7 | } 8 | -------------------------------------------------------------------------------- /front/src/locales/lang/en-US/global.js: -------------------------------------------------------------------------------- 1 | export default { 2 | submit: 'Submit', 3 | save: 'Save', 4 | 'submit.ok': 'Submit successfully', 5 | 'save.ok': 'Saved successfully' 6 | } 7 | -------------------------------------------------------------------------------- /front/src/views/account/center/page/index.js: -------------------------------------------------------------------------------- 1 | import AppPage from './App' 2 | import ArticlePage from './Article' 3 | import ProjectPage from './Project' 4 | 5 | export { AppPage, ArticlePage, ProjectPage } 6 | -------------------------------------------------------------------------------- /front/src/components/AvatarList/index.js: -------------------------------------------------------------------------------- 1 | import AvatarList from './List' 2 | import Item from './Item' 3 | 4 | export { 5 | AvatarList, 6 | Item as AvatarListItem 7 | } 8 | 9 | export default AvatarList 10 | -------------------------------------------------------------------------------- /dao/task.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | type TaskDetail struct { 4 | Host *HostDatabase `json:"host_info"` 5 | Info *TaskInfo `json:"task_info"` 6 | Ding *DingDatabase `json:"ding"` 7 | Oss *OssDatabase `json:"oss"` 8 | } 9 | -------------------------------------------------------------------------------- /front/src/views/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /front/src/layouts/PageView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /front/src/components/index.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/lib/style/index'; 2 | 3 | // The prefix to use on all css classes from ant-pro. 4 | @ant-pro-prefix : ant-pro; 5 | @ant-global-sider-zindex : 106; 6 | @ant-global-header-zindex : 105; 7 | -------------------------------------------------------------------------------- /front/src/views/dashboard/Monitor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /test/date/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | func main() { 9 | for i := 1; i < 10; i++ { 10 | t := time.Now().AddDate(0, 0, -i) 11 | fmt.Println(t.Format("2006-01-02")) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /front/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": ["src/*"] 7 | } 8 | }, 9 | "exclude": ["node_modules", "dist"], 10 | "include": ["src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /front/src/store/device-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | 3 | const deviceMixin = { 4 | computed: { 5 | ...mapState({ 6 | isMobile: state => state.app.isMobile 7 | }) 8 | } 9 | } 10 | 11 | export { deviceMixin } 12 | -------------------------------------------------------------------------------- /front/src/layouts/BlankLayout.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /staging/crypt/pkg/utils.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "path" 5 | "strings" 6 | ) 7 | 8 | // GetFilePath 去除后缀 9 | func GetFilePath(filePath string) string { 10 | ext := path.Ext(filePath) 11 | return strings.TrimSuffix(filePath, ext) 12 | } 13 | -------------------------------------------------------------------------------- /front/src/components/TextArea/style.less: -------------------------------------------------------------------------------- 1 | .ant-textarea-limit { 2 | position: relative; 3 | 4 | .limit { 5 | position: absolute; 6 | right: 10px; 7 | bottom: 5px; 8 | font-size: 12px; 9 | color: #909399; 10 | background: #fff; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /job/system/task_sync_test.go: -------------------------------------------------------------------------------- 1 | package system 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestTaskSync(t *testing.T) { 10 | task := NewTaskSyncJob(context.TODO(), "* * * * *") 11 | task.Start() 12 | time.Sleep(5 * time.Minute) 13 | } 14 | -------------------------------------------------------------------------------- /front/src/components/Charts/chart.less: -------------------------------------------------------------------------------- 1 | .antv-chart-mini { 2 | position: relative; 3 | width: 100%; 4 | 5 | .chart-wrapper { 6 | position: absolute; 7 | bottom: -28px; 8 | width: 100%; 9 | 10 | /* margin: 0 -5px; 11 | overflow: hidden; */ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/miniotest/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/noovertime7/gin-mysqlbak/public/minio" 4 | 5 | func main() { 6 | client := minio.NewClient("10.20.110.61:9000", "minioadmin", "minioadmin", "mysqlbak", "ceshi", "G:\\gin-mysql\\public\\minio\\test.txt") 7 | client.UploadFile() 8 | } 9 | -------------------------------------------------------------------------------- /front/src/layouts/index.js: -------------------------------------------------------------------------------- 1 | import UserLayout from './UserLayout' 2 | import BlankLayout from './BlankLayout' 3 | import BasicLayout from './BasicLayout' 4 | import RouteView from './RouteView' 5 | import PageView from './PageView' 6 | 7 | export { UserLayout, BasicLayout, BlankLayout, RouteView, PageView } 8 | -------------------------------------------------------------------------------- /server/context.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import "context" 4 | 5 | var Cancle context.CancelFunc 6 | 7 | func GetGlobalContext() context.Context { 8 | mainContext := context.Background() 9 | sonContext, cancel := context.WithCancel(mainContext) 10 | Cancle = cancel 11 | return sonContext 12 | } 13 | -------------------------------------------------------------------------------- /front/src/components/Charts/smooth.area.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @smoothArea-prefix-cls: ~"@{ant-pro-prefix}-smooth-area"; 4 | 5 | .@{smoothArea-prefix-cls} { 6 | position: relative; 7 | width: 100%; 8 | 9 | .chart-wrapper { 10 | position: absolute; 11 | bottom: -28px; 12 | width: 100%; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | package-lock.json 23 | -------------------------------------------------------------------------------- /test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gorhill/cronexpr" 6 | "time" 7 | ) 8 | 9 | func main() { 10 | expr, err := cronexpr.Parse("*/5 * * * * * * ") // 如果表达式解析错误将返回一个错误 11 | if err != nil { 12 | fmt.Println(err) 13 | return 14 | } 15 | nextTime := expr.Next(time.Now()) 16 | fmt.Println(nextTime) 17 | } 18 | -------------------------------------------------------------------------------- /front/src/store/i18n-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | 3 | const i18nMixin = { 4 | computed: { 5 | ...mapState({ 6 | currentLang: state => state.app.lang 7 | }) 8 | }, 9 | methods: { 10 | setLang (lang) { 11 | this.$store.dispatch('setLang', lang) 12 | } 13 | } 14 | } 15 | 16 | export default i18nMixin 17 | -------------------------------------------------------------------------------- /front/src/core/icons.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom icon list 3 | * All icons are loaded here for easy management 4 | * @see https://vue.ant.design/components/icon/#Custom-Font-Icon 5 | * 6 | * 自定义图标加载表 7 | * 所有图标均从这里加载,方便管理 8 | */ 9 | import bxAnaalyse from '@/assets/icons/bx-analyse.svg?inline' // path to your '*.svg?inline' file. 10 | 11 | export { bxAnaalyse } 12 | -------------------------------------------------------------------------------- /front/src/views/account/settings/Binding.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /front/src/views/account/settings/Notification.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 22 | 23 | 26 | -------------------------------------------------------------------------------- /front/.gitattributes: -------------------------------------------------------------------------------- 1 | public/* linguist-vendored 2 | 3 | # Automatically normalize line endings (to LF) for all text-based files. 4 | * text=auto eol=lf 5 | 6 | # Declare files that will always have CRLF line endings on checkout. 7 | *.{cmd,[cC][mM][dD]} text eol=crlf 8 | *.{bat,[bB][aA][tT]} text eol=crlf 9 | 10 | # Denote all files that are truly binary and should not be modified. 11 | *.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary -------------------------------------------------------------------------------- /front/src/api/public.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | // 用于获取下载文件的url,并且检测文件是否在本地存在 4 | export function checkDownloadBakFile(id) { 5 | return request({ 6 | url: '/public/check_file_exists', 7 | method: 'get', 8 | params: { id } 9 | }) 10 | } 11 | 12 | // 用于程序异常终止后。调用函数可批量启动运行中任务 13 | export function initBakTask() { 14 | return request({ 15 | url: '/public/initbak', 16 | method: 'get' 17 | }) 18 | } 19 | 20 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/result/fail.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'result.fail.error.title': '提交失败', 3 | 'result.fail.error.description': '请核对并修改以下信息后,再重新提交。', 4 | 'result.fail.error.hint-title': '您提交的内容有如下错误:', 5 | 'result.fail.error.hint-text1': '您的账户已被冻结', 6 | 'result.fail.error.hint-btn1': '立即解冻', 7 | 'result.fail.error.hint-text2': '您的账户还不具备申请资格', 8 | 'result.fail.error.hint-btn2': '立即升级', 9 | 'result.fail.error.btn-text': '返回修改' 10 | } 11 | -------------------------------------------------------------------------------- /front/src/views/list/search/components/IconText.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 23 | -------------------------------------------------------------------------------- /front/.github/ISSUE_TEMPLATE/need-help-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Need help issue 3 | about: Question for use(问题求助) 4 | title: '' 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Question (问题描述)** 11 | How to use component `s-table` paging 12 | 13 | **Describe the solution you'd like (你期待的是什么?)** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Additional context(附加信息)** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /front/src/views/exception/403.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /front/src/views/exception/404.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /front/src/views/exception/500.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /agent/proto/esbak/es_bak.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package go.micro.service.backupAgent; 3 | option go_package="./;EsBak"; 4 | 5 | 6 | 7 | service EsBakService { 8 | rpc Start(StartEsBakInput) returns (EsBakOneMessage) {} 9 | rpc Stop(StopEsBakInput) returns (EsBakOneMessage) {} 10 | } 11 | 12 | 13 | message StartEsBakInput { 14 | int64 TaskID = 1; 15 | } 16 | 17 | message StopEsBakInput { 18 | int64 TaskID = 1; 19 | } 20 | 21 | message EsBakOneMessage { 22 | string Message =1; 23 | bool OK =2; 24 | } 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /front/src/api/dashboard.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function getSvcTNum () { 4 | return request({ 5 | url: 'dashboard/service_task_num', 6 | method: 'get' 7 | }) 8 | } 9 | 10 | export function clusterDataByDate (query) { 11 | return request({ 12 | url: '/dashboard/service_info_by_date', 13 | method: 'get', 14 | params: query 15 | }) 16 | } 17 | 18 | export function getSvcFinishNum () { 19 | return request({ 20 | url: 'dashboard/service_finish_num', 21 | method: 'get' 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /front/src/api/agent.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function GetServiceList (query) { 4 | return request({ 5 | url: '/public/agentlist', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function GetServiceNumInfo () { 12 | return request({ 13 | url: '/public/service_num_info', 14 | method: 'get' 15 | }) 16 | } 17 | 18 | export function DeleteService (query) { 19 | return request({ 20 | url: '/public/delete_agent', 21 | method: 'delete', 22 | params: query 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /front/src/components/FooterToolbar/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @footer-toolbar-prefix-cls: ~"@{ant-pro-prefix}-footer-toolbar"; 4 | 5 | .@{footer-toolbar-prefix-cls} { 6 | position: fixed; 7 | right: 0; 8 | bottom: 0; 9 | z-index: 9; 10 | width: 100%; 11 | height: 56px; 12 | padding: 0 24px; 13 | line-height: 56px; 14 | background: #fff; 15 | box-shadow: 0 -1px 2px rgb(0 0 0 / 3%); 16 | border-top: 1px solid #e8e8e8; 17 | 18 | &::after { 19 | display: block; 20 | clear: both; 21 | content: ''; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | ENV CGO_ENABLED 0 3 | ENV GOOS linux 4 | ENV GOPROXY https://goproxy.cn,direct 5 | WORKDIR /build 6 | COPY . . 7 | RUN go mod download 8 | RUN go build -ldflags="-s -w" -o gin-mysqlbak ./main.go 9 | 10 | FROM centos 11 | WORKDIR /app 12 | ENV TZ Asia/Shanghai 13 | COPY --from=builder /build/gin-mysqlbak /app/gin-mysqlbak 14 | COPY --from=builder /build/conf/config.ini /app/conf/config.ini 15 | COPY --from=builder /build/docker/mysqldump /usr/bin 16 | RUN chmod 777 /usr/bin/mysqldump 17 | EXPOSE 8880 18 | CMD ["./gin-mysqlbak"] -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/e421083458/golang_common/lib" 5 | "github.com/noovertime7/gin-mysqlbak/router" 6 | "log" 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | ) 11 | 12 | func main() { 13 | err := lib.InitModule("./conf/dev/", []string{"base", "mysql"}) 14 | if err != nil { 15 | log.Println("加载配置文件失败", err) 16 | } 17 | defer lib.Destroy() 18 | router.HttpServerRun() 19 | quit := make(chan os.Signal) 20 | signal.Notify(quit, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM) 21 | <-quit 22 | router.HttpServerStop() 23 | } 24 | -------------------------------------------------------------------------------- /front/src/api/agent-history.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function GetAgentHistory (query) { 4 | return request({ 5 | url: '/agent/historylist', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function DeleteHistory (query) { 12 | return request({ 13 | url: '/agent/historydelete', 14 | method: 'delete', 15 | params: query 16 | }) 17 | } 18 | 19 | export function GetAgentNumInfo (query) { 20 | return request({ 21 | url: 'agent/history_num_info', 22 | method: 'get', 23 | params: query 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /front/src/store/getters.js: -------------------------------------------------------------------------------- 1 | const getters = { 2 | isMobile: state => state.app.isMobile, 3 | lang: state => state.app.lang, 4 | theme: state => state.app.theme, 5 | color: state => state.app.color, 6 | token: state => state.user.token, 7 | avatar: state => state.user.avatar, 8 | nickname: state => state.user.name, 9 | welcome: state => state.user.welcome, 10 | roles: state => state.user.roles, 11 | userInfo: state => state.user.info, 12 | addRouters: state => state.permission.addRouters, 13 | multiTab: state => state.app.multiTab 14 | } 15 | 16 | export default getters 17 | -------------------------------------------------------------------------------- /front/src/components/Search/index.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default'; 2 | 3 | .global-search-wrapper { 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | z-index: @zindex-modal-mask; 10 | background: @modal-mask-bg; 11 | 12 | .global-search-box { 13 | position: absolute; 14 | top: 20%; 15 | left: 50%; 16 | width: 450px; 17 | transform: translate(-50%, -50%); 18 | 19 | .global-search-tips { 20 | font-size: @font-size-lg; 21 | color: @white; 22 | text-align: right; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /front/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleFileExtensions: [ 3 | 'js', 4 | 'jsx', 5 | 'json', 6 | 'vue' 7 | ], 8 | transform: { 9 | '^.+\\.vue$': 'vue-jest', 10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 11 | '^.+\\.jsx?$': 'babel-jest' 12 | }, 13 | moduleNameMapper: { 14 | '^@/(.*)$': '/src/$1' 15 | }, 16 | snapshotSerializers: [ 17 | 'jest-serializer-vue' 18 | ], 19 | testMatch: [ 20 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)' 21 | ], 22 | testURL: 'http://localhost/' 23 | } 24 | -------------------------------------------------------------------------------- /front/src/utils/filter.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import moment from 'moment' 3 | import 'moment/locale/zh-cn' 4 | moment.locale('zh-cn') 5 | 6 | Vue.filter('NumberFormat', function (value) { 7 | if (!value) { 8 | return '0' 9 | } 10 | const intPartFormat = value.toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,') // 将整数部分逢三一断 11 | return intPartFormat 12 | }) 13 | 14 | Vue.filter('dayjs', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { 15 | return moment(dataStr).format(pattern) 16 | }) 17 | 18 | Vue.filter('moment', function (dataStr, pattern = 'YYYY-MM-DD HH:mm:ss') { 19 | return moment(dataStr).format(pattern) 20 | }) 21 | -------------------------------------------------------------------------------- /front/src/components/MultiTab/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @multi-tab-prefix-cls: ~"@{ant-pro-prefix}-multi-tab"; 4 | @multi-tab-wrapper-prefix-cls: ~"@{ant-pro-prefix}-multi-tab-wrapper"; 5 | 6 | /* 7 | .topmenu .@{multi-tab-prefix-cls} { 8 | max-width: 1200px; 9 | margin: -23px auto 24px auto; 10 | } 11 | */ 12 | .@{multi-tab-prefix-cls} { 13 | margin: -23px -24px 24px; 14 | background: #fff; 15 | } 16 | 17 | .topmenu .@{multi-tab-wrapper-prefix-cls} { 18 | max-width: 1200px; 19 | margin: 0 auto; 20 | } 21 | 22 | .topmenu.content-width-Fluid .@{multi-tab-wrapper-prefix-cls} { 23 | max-width: 100%; 24 | margin: 0 auto; 25 | } 26 | -------------------------------------------------------------------------------- /front/src/locales/lang/en-US/result/fail.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'result.fail.error.title': 'Submission Failed', 3 | 'result.fail.error.description': 4 | 'Please check and modify the following information before resubmitting.', 5 | 'result.fail.error.hint-title': 'The content you submitted has the following error:', 6 | 'result.fail.error.hint-text1': 'Your account has been frozen', 7 | 'result.fail.error.hint-btn1': 'Thaw immediately', 8 | 'result.fail.error.hint-text2': 'Your account is not yet eligible to apply', 9 | 'result.fail.error.hint-btn2': 'Upgrade immediately', 10 | 'result.fail.error.btn-text': 'Return to modify' 11 | } 12 | -------------------------------------------------------------------------------- /front/src/utils/domUtil.js: -------------------------------------------------------------------------------- 1 | import config from '@/config/defaultSettings' 2 | 3 | export const setDocumentTitle = function (title) { 4 | document.title = title 5 | const ua = navigator.userAgent 6 | // eslint-disable-next-line 7 | const regex = /\bMicroMessenger\/([\d\.]+)/ 8 | if (regex.test(ua) && /ip(hone|od|ad)/i.test(ua)) { 9 | const i = document.createElement('iframe') 10 | i.src = '/favicon.ico' 11 | i.style.display = 'none' 12 | i.onload = function () { 13 | setTimeout(function () { 14 | i.remove() 15 | }, 9) 16 | } 17 | document.body.appendChild(i) 18 | } 19 | } 20 | 21 | export const domTitle = config.title 22 | -------------------------------------------------------------------------------- /front/src/api/host.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function hostAdd (data) { 4 | return request({ 5 | url: '/host/hostadd', 6 | method: 'post', 7 | data 8 | }) 9 | } 10 | 11 | export function hostDelete (id) { 12 | return request({ 13 | url: '/host/hostdelete', 14 | method: 'delete', 15 | params: id 16 | }) 17 | } 18 | 19 | export function hostUpdate (data) { 20 | return request({ 21 | url: '/host/hostupdate', 22 | method: 'post', 23 | data 24 | }) 25 | } 26 | 27 | export function hostList (data) { 28 | return request({ 29 | url: '/host/hostlist', 30 | method: 'get', 31 | params: data 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /front/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import app from './modules/app' 5 | import user from './modules/user' 6 | 7 | // default router permission control 8 | // 默认路由模式为静态路由 (router.config.js) 9 | import permission from './modules/static-router' 10 | 11 | // dynamic router permission control (Experimental) 12 | // 动态路由模式(api请求后端生成) 13 | // import permission from './modules/async-router' 14 | 15 | import getters from './getters' 16 | 17 | Vue.use(Vuex) 18 | 19 | export default new Vuex.Store({ 20 | modules: { 21 | app, 22 | user, 23 | permission 24 | }, 25 | state: {}, 26 | mutations: {}, 27 | actions: {}, 28 | getters 29 | }) 30 | -------------------------------------------------------------------------------- /front/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /staging/crypt/pkg/decrypt_dir.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "github.com/pkg/errors" 6 | "io/ioutil" 7 | ) 8 | 9 | func DecryptDir(key, dir, fileType string) error { 10 | files, err := ioutil.ReadDir(dir) 11 | if err != nil { 12 | return err 13 | } 14 | if len(files) == 0 { 15 | return errors.New("Directory is empty") 16 | } 17 | for _, file := range files { 18 | filePath := dir + "/" + file.Name() 19 | data, err := Decrypt(key, filePath, fileType) 20 | if err != nil { 21 | fmt.Printf("decrypt %v err:%v continue\n", file.Name(), err) 22 | continue 23 | } 24 | fmt.Printf("decrypt %v success, decrypt file : %v\n", file.Name(), data) 25 | } 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /front/.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /front/src/components/SelectLang/index.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default'; 2 | 3 | @header-menu-prefix-cls: ~'@{ant-prefix}-pro-header-menu'; 4 | @header-drop-down-prefix-cls: ~'@{ant-prefix}-pro-drop-down'; 5 | 6 | .@{header-menu-prefix-cls} { 7 | .anticon { 8 | margin-right: 8px; 9 | } 10 | 11 | .ant-dropdown-menu-item { 12 | min-width: 160px; 13 | } 14 | } 15 | 16 | .@{header-drop-down-prefix-cls} { 17 | line-height: @layout-header-height; 18 | vertical-align: top; 19 | cursor: pointer; 20 | 21 | > i { 22 | font-size: 16px !important; 23 | transform: none !important; 24 | 25 | svg { 26 | position: relative; 27 | top: -1px; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /kubernetes/conf/mysqlbak-base-conf.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | data: 4 | base.toml: | 5 | [log] 6 | log_level = "trace" #日志打印最低级别 7 | [log.file_writer] #文件写入配置 8 | on = true 9 | log_path = "./logs/gin-mysqlbak.inf.log" 10 | rotate_log_path = "./logs/gin-mysqlbak.inf.log.%Y%M%D%H" 11 | wf_log_path = "./logs/gin-mysqlbak.wf.log" 12 | rotate_wf_log_path = "./logs/gin-mysqlbak.wf.log.%Y%M%D%H" 13 | [log.console_writer] #工作台输出 14 | on = false 15 | color = false 16 | kind: ConfigMap 17 | metadata: 18 | name: mysqlbak-base-conf 19 | namespace: mysqlbak 20 | resourceVersion: '90333491' 21 | 22 | -------------------------------------------------------------------------------- /front/deploy/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name _; 4 | # gzip config 5 | gzip on; 6 | gzip_min_length 1k; 7 | gzip_comp_level 6; 8 | gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml; 9 | gzip_vary on; 10 | gzip_disable "MSIE [1-6]\."; 11 | 12 | root /usr/share/nginx/html; 13 | include /etc/nginx/mime.types; 14 | 15 | location / { 16 | try_files $uri $uri/ /index.html; 17 | } 18 | 19 | location ^~/api/ { 20 | proxy_pass http://127.0.0.1/; 21 | proxy_set_header X-Forwarded-Proto $scheme; 22 | proxy_set_header X-Real-IP $remote_addr; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /conf/config.go: -------------------------------------------------------------------------------- 1 | package conf 2 | 3 | import ( 4 | "gopkg.in/ini.v1" 5 | "log" 6 | "os" 7 | ) 8 | 9 | var Config *ini.File 10 | 11 | func init() { 12 | if Config != nil { 13 | return 14 | } 15 | path, err := os.Getwd() 16 | cfg, err := ini.Load(path + "/conf/config.ini") 17 | if err != nil { 18 | log.Fatal("Fail to read file: ", err) 19 | } 20 | Config = cfg 21 | } 22 | 23 | func GetStringConf(section, key string) string { 24 | return Config.Section(section).Key(key).MustString("获取string失败") 25 | } 26 | 27 | func GetBoolConf(section, key string) bool { 28 | return Config.Section(section).Key(key).MustBool(false) 29 | } 30 | 31 | func GetIntConf(section, key string) int { 32 | return Config.Section(section).Key(key).MustInt(0) 33 | } 34 | -------------------------------------------------------------------------------- /services/stopAllTask.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/noovertime7/gin-mysqlbak/core" 5 | "github.com/noovertime7/gin-mysqlbak/dao" 6 | "github.com/noovertime7/gin-mysqlbak/public/database" 7 | "github.com/noovertime7/mysqlbak/pkg/log" 8 | ) 9 | 10 | func StopAllUpTask() { 11 | log.Logger.Info("程序终止,停止所有备份任务") 12 | tx := database.GetDB() 13 | taskinfos, err := dao.FindAllStatusUpTask(tx) 14 | var bakhandler core.BakHandler 15 | for _, task := range taskinfos { 16 | err = bakhandler.StopBak(task.Id) 17 | if err != nil { 18 | log.Logger.Error(err) 19 | return 20 | } 21 | task.Status = 0 22 | err = task.UpdatesStatus(tx) 23 | if err != nil { 24 | log.Logger.Error(err) 25 | return 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /front/commitlint.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * feat:新增功能 3 | * fix:bug 修复 4 | * docs:文档更新 5 | * style:不影响程序逻辑的代码修改(修改空白字符,格式缩进,补全缺失的分号等,没有改变代码逻辑) 6 | * refactor:重构代码(既没有新增功能,也没有修复 bug) 7 | * perf:性能, 体验优化 8 | * test:新增测试用例或是更新现有测试 9 | * build:主要目的是修改项目构建系统(例如 glup,webpack,rollup 的配置等)的提交 10 | * ci:主要目的是修改项目继续集成流程(例如 Travis,Jenkins,GitLab CI,Circle等)的提交 11 | * chore:不属于以上类型的其他类型,比如构建流程, 依赖管理 12 | * revert:回滚某个更早之前的提交 13 | */ 14 | 15 | module.exports = { 16 | extends: ['@commitlint/config-conventional'], 17 | rules: { 18 | 'type-enum': [ 19 | 2, 20 | 'always', 21 | ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert'] 22 | ], 23 | 'subject-full-stop': [0, 'never'], 24 | 'subject-case': [0, 'never'] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /front/src/utils/axios.js: -------------------------------------------------------------------------------- 1 | const VueAxios = { 2 | vm: {}, 3 | // eslint-disable-next-line no-unused-vars 4 | install (Vue, instance) { 5 | if (this.installed) { 6 | return 7 | } 8 | this.installed = true 9 | 10 | if (!instance) { 11 | // eslint-disable-next-line no-console 12 | console.error('You have to install axios') 13 | return 14 | } 15 | 16 | Vue.axios = instance 17 | 18 | Object.defineProperties(Vue.prototype, { 19 | axios: { 20 | get: function get () { 21 | return instance 22 | } 23 | }, 24 | $http: { 25 | get: function get () { 26 | return instance 27 | } 28 | } 29 | }) 30 | } 31 | } 32 | 33 | export { 34 | VueAxios 35 | } 36 | -------------------------------------------------------------------------------- /front/babel.config.js: -------------------------------------------------------------------------------- 1 | const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) 2 | const IS_PREVIEW = process.env.VUE_APP_PREVIEW === 'true' 3 | 4 | const plugins = [] 5 | if (IS_PROD && !IS_PREVIEW) { 6 | // 去除日志的插件, 7 | plugins.push('transform-remove-console') 8 | } 9 | 10 | // lazy load ant-design-vue 11 | // if your use import on Demand, Use this code 12 | plugins.push(['import', { 13 | 'libraryName': 'ant-design-vue', 14 | 'libraryDirectory': 'es', 15 | 'style': true // `style: true` 会加载 less 文件 16 | }]) 17 | 18 | module.exports = { 19 | presets: [ 20 | '@vue/cli-plugin-babel/preset', 21 | [ 22 | '@babel/preset-env', 23 | { 24 | 'useBuiltIns': 'entry', 25 | 'corejs': 3 26 | } 27 | ] 28 | ], 29 | plugins 30 | } 31 | -------------------------------------------------------------------------------- /front/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [项目下载和运行](#%E9%A1%B9%E7%9B%AE%E4%B8%8B%E8%BD%BD%E5%92%8C%E8%BF%90%E8%A1%8C) 6 | 7 | 8 | 9 | 项目下载和运行 10 | ---- 11 | 12 | - 拉取项目代码 13 | ```bash 14 | git clone https://github.com/noovertime7/gin-mysqlbak.git 15 | cd front 16 | ``` 17 | 18 | - 安装依赖 19 | ``` 20 | yarn install 21 | ``` 22 | 23 | - 开发模式运行 24 | ``` 25 | yarn run serve 26 | ``` 27 | 28 | - 编译项目 29 | ``` 30 | yarn run build 31 | ``` 32 | 33 | - Lints and fixes files 34 | ``` 35 | yarn run lint 36 | ``` 37 | -------------------------------------------------------------------------------- /front/src/components/Trend/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @trend-prefix-cls: ~"@{ant-pro-prefix}-trend"; 4 | 5 | .@{trend-prefix-cls} { 6 | display: inline-block; 7 | font-size: @font-size-base; 8 | line-height: 22px; 9 | 10 | .up, 11 | .down { 12 | position: relative; 13 | top: 1px; 14 | margin-left: 4px; 15 | 16 | i { 17 | font-size: 12px; 18 | transform: scale(.83); 19 | } 20 | } 21 | 22 | .item-text { 23 | display: inline-block; 24 | margin-left: 8px; 25 | color: rgb(0 0 0 / 85%); 26 | } 27 | 28 | .up { 29 | color: @red-6; 30 | } 31 | 32 | .down { 33 | top: -1px; 34 | color: @green-6; 35 | } 36 | 37 | &.reverse-color .up { 38 | color: @green-6; 39 | } 40 | 41 | &.reverse-color .down { 42 | color: @red-6; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /front/src/mock/index.js: -------------------------------------------------------------------------------- 1 | import { isIE } from '@/utils/util' 2 | 3 | // 判断环境不是 prod 或者 preview 是 true 时,加载 mock 服务 4 | if (process.env.NODE_ENV !== 'production' || process.env.VUE_APP_PREVIEW === 'true') { 5 | if (isIE()) { 6 | console.error('[antd-pro] ERROR: `mockjs` NOT SUPPORT `IE` PLEASE DO NOT USE IN `production` ENV.') 7 | } 8 | // 使用同步加载依赖 9 | // 防止 vuex 中的 GetInfo 早于 mock 运行,导致无法 mock 请求返回结果 10 | console.log('[antd-pro] mock mounting') 11 | const Mock = require('mockjs2') 12 | require('./services/auth') 13 | require('./services/user') 14 | require('./services/manage') 15 | require('./services/other') 16 | require('./services/tagCloud') 17 | require('./services/article') 18 | 19 | Mock.setup({ 20 | timeout: 800 // setter delay time 21 | }) 22 | console.log('[antd-pro] mock mounted') 23 | } 24 | -------------------------------------------------------------------------------- /front/src/components/SettingDrawer/SettingItem.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 24 | 25 | 39 | -------------------------------------------------------------------------------- /front/src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | export const ACCESS_TOKEN = 'Access-Token' 2 | 3 | export const SIDEBAR_TYPE = 'sidebar_type' 4 | export const TOGGLE_MOBILE_TYPE = 'is_mobile' 5 | export const TOGGLE_NAV_THEME = 'nav_theme' 6 | export const TOGGLE_LAYOUT = 'layout' 7 | export const TOGGLE_FIXED_HEADER = 'fixed_header' 8 | export const TOGGLE_FIXED_SIDEBAR = 'fixed_sidebar' 9 | export const TOGGLE_CONTENT_WIDTH = 'content_width' 10 | export const TOGGLE_HIDE_HEADER = 'auto_hide_header' 11 | export const TOGGLE_COLOR = 'color' 12 | export const TOGGLE_WEAK = 'weak' 13 | export const TOGGLE_MULTI_TAB = 'multi_tab' 14 | export const APP_LANGUAGE = 'app_language' 15 | 16 | export const CONTENT_WIDTH_TYPE = { 17 | Fluid: 'Fluid', 18 | Fixed: 'Fixed' 19 | } 20 | 21 | export const NAV_THEME = { 22 | LIGHT: 'light', 23 | DARK: 'dark' 24 | } 25 | -------------------------------------------------------------------------------- /front/src/utils/routeConvert.js: -------------------------------------------------------------------------------- 1 | import cloneDeep from 'lodash.clonedeep' 2 | 3 | export function convertRoutes (nodes) { 4 | if (!nodes) return null 5 | 6 | nodes = cloneDeep(nodes) 7 | 8 | let queue = Array.isArray(nodes) ? nodes.concat() : [nodes] 9 | 10 | while (queue.length) { 11 | const levelSize = queue.length 12 | 13 | for (let i = 0; i < levelSize; i++) { 14 | const node = queue.shift() 15 | 16 | if (!node.children || !node.children.length) continue 17 | 18 | node.children.forEach(child => { 19 | // 转化相对路径 20 | if (child.path[0] !== '/' && !child.path.startsWith('http')) { 21 | child.path = node.path.replace(/(\w*)[/]*$/, `$1/${child.path}`) 22 | } 23 | }) 24 | 25 | queue = queue.concat(node.children) 26 | } 27 | } 28 | 29 | return nodes 30 | } 31 | -------------------------------------------------------------------------------- /front/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset=utf-8 3 | end_of_line=lf 4 | insert_final_newline=false 5 | indent_style=space 6 | indent_size=2 7 | 8 | [{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}] 9 | indent_style=space 10 | indent_size=2 11 | 12 | [{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}] 13 | indent_style=space 14 | indent_size=2 15 | 16 | [{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}] 17 | indent_style=space 18 | indent_size=2 19 | 20 | [*.svg] 21 | indent_style=space 22 | indent_size=2 23 | 24 | [*.js.map] 25 | indent_style=space 26 | indent_size=2 27 | 28 | [*.less] 29 | indent_style=space 30 | indent_size=2 31 | 32 | [*.vue] 33 | indent_style=space 34 | indent_size=2 35 | 36 | [{.analysis_options,*.yml,*.yaml}] 37 | indent_style=space 38 | indent_size=2 39 | 40 | -------------------------------------------------------------------------------- /front/src/components/GlobalFooter/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | -------------------------------------------------------------------------------- /front/src/layouts/RouteView.vue: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /front/src/api/task.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function taskList (data) { 4 | return request({ 5 | url: '/task/tasklist', 6 | method: 'get', 7 | params: data 8 | }) 9 | } 10 | 11 | export function taskDelete (query) { 12 | return request({ 13 | url: '/task/taskdelete', 14 | method: 'delete', 15 | params: query 16 | }) 17 | } 18 | 19 | export function taskAdd (data) { 20 | return request({ 21 | url: '/task/taskadd', 22 | method: 'post', 23 | data 24 | }) 25 | } 26 | 27 | export function taskUpdate (data) { 28 | return request({ 29 | url: '/task/taskupdate', 30 | method: 'put', 31 | data 32 | }) 33 | } 34 | 35 | export function taskDetail (query) { 36 | return request({ 37 | url: '/task/taskdetail', 38 | method: 'get', 39 | params: query 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /front/src/core/use.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | // base library 4 | import Antd from 'ant-design-vue' 5 | import Viser from 'viser-vue' 6 | import VueCropper from 'vue-cropper' 7 | import 'ant-design-vue/dist/antd.less' 8 | 9 | // ext library 10 | import VueClipboard from 'vue-clipboard2' 11 | import MultiTab from '@/components/MultiTab' 12 | import PageLoading from '@/components/PageLoading' 13 | import PermissionHelper from '@/core/permission/permission' 14 | // import '@/components/use' 15 | import './directives/action' 16 | 17 | VueClipboard.config.autoSetContainer = true 18 | 19 | Vue.use(Antd) 20 | Vue.use(Viser) 21 | Vue.use(MultiTab) 22 | Vue.use(PageLoading) 23 | Vue.use(VueClipboard) 24 | Vue.use(PermissionHelper) 25 | Vue.use(VueCropper) 26 | 27 | process.env.NODE_ENV !== 'production' && console.warn('[antd-pro] WARNING: Antd now use fulled imported.') 28 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/result/success.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'result.success.title': '提交成功', 3 | 'result.success.description': 4 | '提交结果页用于反馈一系列操作任务的处理结果, 如果仅是简单操作,使用 Message 全局提示反馈即可。 本文字区域可以展示简单的补充说明,如果有类似展示 “单据”的需求,下面这个灰色区域可以呈现比较复杂的内容。', 5 | 'result.success.operate-title': '项目名称', 6 | 'result.success.operate-id': '项目 ID', 7 | 'result.success.principal': '负责人', 8 | 'result.success.operate-time': '生效时间', 9 | 'result.success.step1-title': '创建项目', 10 | 'result.success.step1-operator': '曲丽丽', 11 | 'result.success.step2-title': '部门初审', 12 | 'result.success.step2-operator': '周毛毛', 13 | 'result.success.step2-extra': '催一下', 14 | 'result.success.step3-title': '财务复核', 15 | 'result.success.step4-title': '完成', 16 | 'result.success.btn-return': '返回列表', 17 | 'result.success.btn-project': '查看项目', 18 | 'result.success.btn-print': '打印' 19 | } 20 | -------------------------------------------------------------------------------- /front/src/layouts/BasicLayout.less: -------------------------------------------------------------------------------- 1 | @import '~ant-design-vue/es/style/themes/default.less'; 2 | 3 | .ant-pro-global-header-index-right { 4 | margin-right: 8px; 5 | 6 | &.ant-pro-global-header-index-dark { 7 | .ant-pro-global-header-index-action { 8 | color: hsl(0deg 0% 100% / 85%); 9 | 10 | &:hover { 11 | background: #1890ff; 12 | } 13 | } 14 | } 15 | 16 | .ant-pro-account-avatar { 17 | .antd-pro-global-header-index-avatar { 18 | margin: ~'calc((@{layout-header-height} - 24px) / 2)' 0; 19 | margin-right: 8px; 20 | color: @primary-color; 21 | vertical-align: top; 22 | background: rgb(255 255 255 / 85%); 23 | } 24 | } 25 | 26 | .menu { 27 | .anticon { 28 | margin-right: 8px; 29 | } 30 | 31 | .ant-dropdown-menu-item { 32 | min-width: 100px; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /agent/agentdto/es_bak.go: -------------------------------------------------------------------------------- 1 | package agentdto 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/noovertime7/gin-mysqlbak/public" 6 | ) 7 | 8 | type ESBakStartInput struct { 9 | ID int64 `json:"task_id" form:"task_id" comment:"ID" validate:"required"` 10 | ServiceName string `json:"service_name" form:"service_name" comment:"服务名" validate:"required"` 11 | } 12 | 13 | type ESBakStopInput struct { 14 | ID int64 `json:"task_id" form:"task_id" comment:"ID" validate:"required"` 15 | ServiceName string `json:"service_name" form:"service_name" comment:"服务名" validate:"required"` 16 | } 17 | 18 | func (d *ESBakStopInput) BindValidParam(ctx *gin.Context) error { 19 | return public.DefaultGetValidParams(ctx, d) 20 | } 21 | 22 | func (d *ESBakStartInput) BindValidParam(ctx *gin.Context) error { 23 | return public.DefaultGetValidParams(ctx, d) 24 | } 25 | -------------------------------------------------------------------------------- /front/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import { constantRouterMap } from '@/config/router.config' 4 | 5 | // hack router push callback 6 | const originalPush = Router.prototype.push 7 | Router.prototype.push = function push (location, onResolve, onReject) { 8 | if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject) 9 | return originalPush.call(this, location).catch(err => err) 10 | } 11 | 12 | Vue.use(Router) 13 | 14 | const createRouter = () => 15 | new Router({ 16 | mode: 'history', 17 | routes: constantRouterMap 18 | }) 19 | 20 | const router = createRouter() 21 | 22 | // 定义一个resetRouter 方法,在退出登录后或token过期后 需要重新登录时,调用即可 23 | export function resetRouter () { 24 | const newRouter = createRouter() 25 | router.matcher = newRouter.matcher 26 | } 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /front/src/components/Trend/Trend.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /staging/crypt/pkg/decrypt.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "io/ioutil" 7 | ) 8 | 9 | func Decrypt(key, filePath, fileType string) (string, error) { 10 | ciphertext, err := ioutil.ReadFile(filePath) 11 | if err != nil { 12 | return "", err 13 | } 14 | block, err := aes.NewCipher([]byte(key)) 15 | if err != nil { 16 | return "", err 17 | } 18 | gcm, err := cipher.NewGCM(block) 19 | if err != nil { 20 | return "", err 21 | } 22 | nonce := ciphertext[:gcm.NonceSize()] 23 | ciphertext = ciphertext[gcm.NonceSize():] 24 | plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) 25 | if err != nil { 26 | return "", err 27 | } 28 | tempPath := GetFilePath(filePath) 29 | deFileName := tempPath + "." + fileType 30 | if err := ioutil.WriteFile(deFileName, plaintext, 0777); err != nil { 31 | return "", err 32 | } 33 | return deFileName, nil 34 | } 35 | -------------------------------------------------------------------------------- /dto/admin_login.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/noovertime7/gin-mysqlbak/public" 6 | "time" 7 | ) 8 | 9 | type AdminLoginInput struct { 10 | UserName string `form:"username" json:"username" comment:"用户名" validate:"required" example:"admin"` 11 | Password string `form:"password" json:"password" comment:"密码" validate:"required" example:"123456"` 12 | } 13 | 14 | type AdminLoginOut struct { 15 | Message string `json:"message"` 16 | Token string `form:"token" json:"token" comment:"token" example:"token"` 17 | } 18 | 19 | type AdminSessionInfo struct { 20 | ID int `json:"id"` 21 | UserName string `json:"username"` 22 | LoginTime time.Time `json:"login_time"` 23 | } 24 | 25 | // BindValidParams 绑定并校验参数 26 | func (a *AdminLoginInput) BindValidParams(ctx *gin.Context) error { 27 | return public.DefaultGetValidParams(ctx, a) 28 | } 29 | -------------------------------------------------------------------------------- /staging/crypt/pkg/encrypt.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | "io" 8 | "io/ioutil" 9 | ) 10 | 11 | func Encrypt(key, filePath, fileType string) (string, error) { 12 | plaintext, err := ioutil.ReadFile(filePath) 13 | if err != nil { 14 | return "", err 15 | } 16 | block, err := aes.NewCipher([]byte(key)) 17 | if err != nil { 18 | return "", err 19 | } 20 | gcm, err := cipher.NewGCM(block) 21 | if err != nil { 22 | return "", err 23 | } 24 | nonce := make([]byte, gcm.NonceSize()) 25 | if _, err := io.ReadFull(rand.Reader, nonce); err != nil { 26 | return "", err 27 | } 28 | ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) 29 | // Save back to file 30 | cyName := GetFilePath(filePath) + "." + fileType 31 | err = ioutil.WriteFile(cyName, ciphertext, 0777) 32 | if err != nil { 33 | return "", err 34 | } 35 | return cyName, nil 36 | } 37 | -------------------------------------------------------------------------------- /front/src/store/modules/async-router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 向后端请求用户的菜单,动态生成路由 3 | */ 4 | import { constantRouterMap } from '@/config/router.config' 5 | import { generatorDynamicRouter } from '@/router/generator-routers' 6 | 7 | const permission = { 8 | state: { 9 | routers: constantRouterMap, 10 | addRouters: [] 11 | }, 12 | mutations: { 13 | SET_ROUTERS: (state, routers) => { 14 | state.addRouters = routers 15 | state.routers = constantRouterMap.concat(routers) 16 | } 17 | }, 18 | actions: { 19 | GenerateRoutes ({ commit }, data) { 20 | return new Promise((resolve, reject) => { 21 | const { token } = data 22 | generatorDynamicRouter(token).then(routers => { 23 | commit('SET_ROUTERS', routers) 24 | resolve() 25 | }).catch(e => { 26 | reject(e) 27 | }) 28 | }) 29 | } 30 | } 31 | } 32 | 33 | export default permission 34 | -------------------------------------------------------------------------------- /staging/crypt/cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/spf13/cobra" 6 | "os" 7 | ) 8 | 9 | // rootCmd represents the base command when called without any subcommands 10 | var rootCmd = &cobra.Command{ 11 | Use: "crypt", 12 | Short: "An encryption and decryption tool using AES", 13 | Long: `Encrypt or decrypt files using the GCM algorithm in AES encryption`, 14 | } 15 | 16 | func init() { 17 | rootCmd.PersistentFlags().StringP("key", "k", "0123456789abcdeasbgted3jikydj3ss", " crypt -k 0123456789abcdeasbgted3jikydj3ss ") 18 | if err := rootCmd.MarkPersistentFlagRequired("key"); err != nil { 19 | HandleErr("MarkPersistentFlagRequired err", err) 20 | } 21 | } 22 | 23 | func Execute() { 24 | if err := rootCmd.Execute(); err != nil { 25 | HandleErr("Execute err", err) 26 | } 27 | } 28 | 29 | func HandleErr(msg string, err error) { 30 | fmt.Printf("%s:%v\n", msg, err) 31 | os.Exit(1) 32 | } 33 | -------------------------------------------------------------------------------- /dao/roledao/action.go: -------------------------------------------------------------------------------- 1 | package roledao 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type ActionDB struct { 9 | Id int `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"` 10 | PermissionId int `gorm:"column:permission_id;type:int(11)" json:"permission_id"` 11 | Describe string `gorm:"column:describe;type:varchar(20)" json:"describe"` 12 | DefaultCheck int `gorm:"column:default_check;type:int(11)" json:"default_check"` 13 | Action string `gorm:"column:action;type:varchar(20)" json:"action"` 14 | } 15 | 16 | func (a *ActionDB) TableName() string { 17 | return "t_action" 18 | } 19 | 20 | func (a *ActionDB) FindActions(ctx *gin.Context, tx *gorm.DB, search *ActionDB) ([]*ActionDB, error) { 21 | var out []*ActionDB 22 | if err := tx.WithContext(ctx).Where(search).Find(&out).Error; err != nil { 23 | return nil, err 24 | } 25 | return out, nil 26 | } 27 | -------------------------------------------------------------------------------- /front/docs/webpack-bundle-analyzer.md: -------------------------------------------------------------------------------- 1 | 先增加依赖 2 | 3 | ```bash 4 | // npm 5 | $ npm install --save-dev webpack-bundle-analyzer 6 | 7 | // or yarn 8 | $ yarn add webpack-bundle-analyzer -D 9 | ``` 10 | 11 | 配置文件 `vue.config.js` 增加 `configureWebpack.plugins` 参数 12 | 13 | ``` 14 | const path = require('path') 15 | const webpack = require('webpack') 16 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 17 | 18 | function resolve (dir) { 19 | return path.join(__dirname, dir) 20 | } 21 | 22 | // vue.config.js 23 | module.exports = { 24 | configureWebpack: { 25 | plugins: [ 26 | // Ignore all locale files of moment.js 27 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 28 | // 依赖大小分析工具 29 | new BundleAnalyzerPlugin(), 30 | ] 31 | }, 32 | 33 | 34 | ... 35 | } 36 | ``` 37 | 38 | 39 | 40 | 启动 `cli` 的 `build` 命令进行项目编译,编译完成时,会自动运行一个 http://localhost:8888 的地址,完整显示了支持库依赖 -------------------------------------------------------------------------------- /front/src/store/app-mixin.js: -------------------------------------------------------------------------------- 1 | import { mapState } from 'vuex' 2 | 3 | const baseMixin = { 4 | computed: { 5 | ...mapState({ 6 | layout: state => state.app.layout, 7 | navTheme: state => state.app.theme, 8 | primaryColor: state => state.app.color, 9 | colorWeak: state => state.app.weak, 10 | fixedHeader: state => state.app.fixedHeader, 11 | fixedSidebar: state => state.app.fixedSidebar, 12 | contentWidth: state => state.app.contentWidth, 13 | autoHideHeader: state => state.app.autoHideHeader, 14 | 15 | isMobile: state => state.app.isMobile, 16 | sideCollapsed: state => state.app.sideCollapsed, 17 | multiTab: state => state.app.multiTab 18 | }), 19 | isTopMenu () { 20 | return this.layout === 'topmenu' 21 | } 22 | }, 23 | methods: { 24 | isSideMenu () { 25 | return !this.isTopMenu 26 | } 27 | } 28 | } 29 | 30 | export { 31 | baseMixin 32 | } 33 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN.js: -------------------------------------------------------------------------------- 1 | import antd from 'ant-design-vue/es/locale-provider/zh_CN' 2 | import momentCN from 'moment/locale/zh-cn' 3 | import global from './zh-CN/global' 4 | 5 | import menu from './zh-CN/menu' 6 | import setting from './zh-CN/setting' 7 | import user from './zh-CN/user' 8 | import dashboard from './zh-CN/dashboard' 9 | import form from './zh-CN/form' 10 | import result from './zh-CN/result' 11 | import account from './zh-CN/account' 12 | 13 | const components = { 14 | antLocale: antd, 15 | momentName: 'zh-cn', 16 | momentLocale: momentCN 17 | } 18 | 19 | export default { 20 | message: '-', 21 | 22 | 'layouts.usermenu.dialog.title': '信息', 23 | 'layouts.usermenu.dialog.content': '您确定要注销吗?', 24 | 'layouts.userLayout.title': 'TBak System 一款简单高效的备份系统', 25 | ...components, 26 | ...global, 27 | ...menu, 28 | ...setting, 29 | ...user, 30 | ...dashboard, 31 | ...form, 32 | ...result, 33 | ...account 34 | } 35 | -------------------------------------------------------------------------------- /front/src/components/MultiTab/index.js: -------------------------------------------------------------------------------- 1 | import events from './events' 2 | import MultiTab from './MultiTab' 3 | import './index.less' 4 | 5 | const api = { 6 | /** 7 | * open new tab on route fullPath 8 | * @param config 9 | */ 10 | open: function (config) { 11 | events.$emit('open', config) 12 | }, 13 | rename: function (key, name) { 14 | events.$emit('rename', { key: key, name: name }) 15 | }, 16 | /** 17 | * close current page 18 | */ 19 | closeCurrentPage: function () { 20 | this.close() 21 | }, 22 | /** 23 | * close route fullPath tab 24 | * @param config 25 | */ 26 | close: function (config) { 27 | events.$emit('close', config) 28 | } 29 | } 30 | 31 | MultiTab.install = function (Vue) { 32 | if (Vue.prototype.$multiTab) { 33 | return 34 | } 35 | api.instance = events 36 | Vue.prototype.$multiTab = api 37 | Vue.component('multi-tab', MultiTab) 38 | } 39 | 40 | export default MultiTab 41 | -------------------------------------------------------------------------------- /agent/pkg/trace/jaeger.go: -------------------------------------------------------------------------------- 1 | package trace 2 | 3 | import ( 4 | "github.com/opentracing/opentracing-go" 5 | "github.com/uber/jaeger-client-go" 6 | jaegercfg "github.com/uber/jaeger-client-go/config" 7 | "io" 8 | "time" 9 | ) 10 | 11 | // NewJaegerTracer 配置jaeger 12 | func NewJaegerTracer(serviceName string, addr string) (opentracing.Tracer, io.Closer, error) { 13 | cfg := jaegercfg.Configuration{ 14 | ServiceName: serviceName, 15 | Sampler: &jaegercfg.SamplerConfig{ 16 | Type: jaeger.SamplerTypeConst, 17 | Param: 1, 18 | }, 19 | Reporter: &jaegercfg.ReporterConfig{ 20 | LogSpans: true, 21 | BufferFlushInterval: 1 * time.Second, 22 | }, 23 | } 24 | 25 | sender, err := jaeger.NewUDPTransport(addr, 0) 26 | if err != nil { 27 | return nil, nil, err 28 | } 29 | 30 | reporter := jaeger.NewRemoteReporter(sender) 31 | tracer, closer, err := cfg.NewTracer( 32 | jaegercfg.Reporter(reporter), 33 | ) 34 | 35 | return tracer, closer, err 36 | } 37 | -------------------------------------------------------------------------------- /dao/roledao/permission.go: -------------------------------------------------------------------------------- 1 | package roledao 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type PermissionDB struct { 9 | Id int `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"` 10 | PermissionName string `gorm:"column:permission_name;type:varchar(20)" json:"permission_name"` 11 | PermissionId string `gorm:"column:permission_id;type:varchar(20)" json:"permission_id"` 12 | Actions string `gorm:"column:actions;type:text" json:"actions"` 13 | RoleId string `gorm:"column:role_id;type:varchar(20)" json:"role_id"` 14 | } 15 | 16 | func (p *PermissionDB) TableName() string { 17 | return "t_permission" 18 | } 19 | 20 | func (p *PermissionDB) FindPermissions(ctx *gin.Context, tx *gorm.DB, search *PermissionDB) ([]*PermissionDB, error) { 21 | var out []*PermissionDB 22 | if err := tx.WithContext(ctx).Where(search).Find(&out).Error; err != nil { 23 | return nil, err 24 | } 25 | return out, nil 26 | } 27 | -------------------------------------------------------------------------------- /front/src/components/SettingDrawer/themeColor.js: -------------------------------------------------------------------------------- 1 | import client from 'webpack-theme-color-replacer/client' 2 | import generate from '@ant-design/colors/lib/generate' 3 | 4 | export default { 5 | getAntdSerials (color) { 6 | // 淡化(即less的tint) 7 | const lightens = new Array(9).fill().map((t, i) => { 8 | return client.varyColor.lighten(color, i / 10) 9 | }) 10 | // colorPalette变换得到颜色值 11 | const colorPalettes = generate(color) 12 | const rgb = client.varyColor.toNum3(color.replace('#', '')).join(',') 13 | return lightens.concat(colorPalettes).concat(rgb) 14 | }, 15 | changeColor (newColor) { 16 | var options = { 17 | newColors: this.getAntdSerials(newColor), // new colors array, one-to-one corresponde with `matchColors` 18 | changeUrl (cssUrl) { 19 | return `/${cssUrl}` // while router is not `hash` mode, it needs absolute path 20 | } 21 | } 22 | return client.changer.changeColor(options, Promise) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /kubernetes/conf/mysqlbak-web-nginx-default.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | default.conf: |+ 4 | server { 5 | listen 80; 6 | server_name _; 7 | # gzip config 8 | gzip on; 9 | gzip_min_length 1k; 10 | gzip_comp_level 6; 11 | gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml; 12 | gzip_vary on; 13 | gzip_disable "MSIE [1-6]\."; 14 | 15 | root /usr/share/nginx/html; 16 | include /etc/nginx/mime.types; 17 | 18 | location / { 19 | try_files $uri $uri/ /index.html; 20 | } 21 | 22 | location ^~/api { 23 | proxy_pass http://127.0.0.1/; 24 | proxy_set_header X-Forwarded-Proto $scheme; 25 | proxy_set_header X-Real-IP $remote_addr; 26 | } 27 | } 28 | 29 | kind: ConfigMap 30 | metadata: 31 | name: mysqlbak-web-conf 32 | namespace: mysqlbak -------------------------------------------------------------------------------- /front/src/components/AvatarList/Item.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'ant-design-vue/es/_util/vue-types' 2 | import { Tooltip, Avatar } from 'ant-design-vue' 3 | import { getSlotOptions } from 'ant-design-vue/lib/_util/props-util' 4 | import { warning } from 'ant-design-vue/lib/vc-util/warning' 5 | 6 | export const AvatarListItemProps = { 7 | tips: PropTypes.string, 8 | src: PropTypes.string.def('') 9 | } 10 | 11 | const Item = { 12 | __ANT_AVATAR_CHILDREN: true, 13 | name: 'AvatarListItem', 14 | props: AvatarListItemProps, 15 | created () { 16 | warning(getSlotOptions(this.$parent).__ANT_AVATAR_LIST, 'AvatarListItem must be a subcomponent of AvatarList') 17 | }, 18 | render () { 19 | const size = this.$parent.size === 'mini' ? 'small' : this.$parent.size 20 | const AvatarDom = 21 | return (this.tips && {AvatarDom}) || 22 | } 23 | } 24 | 25 | export default Item 26 | -------------------------------------------------------------------------------- /agent/agentcontroller/dashboard.go: -------------------------------------------------------------------------------- 1 | package agentcontroller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/noovertime7/gin-mysqlbak/agent/agentdto" 6 | "github.com/noovertime7/gin-mysqlbak/middleware" 7 | ) 8 | 9 | type DashBoardController struct{} 10 | 11 | func DashBoardRegister(group *gin.RouterGroup) { 12 | dashboard := &DashBoardController{} 13 | group.GET("/barchart", dashboard.GetBarChartData) 14 | } 15 | 16 | func (d *DashBoardController) GetBarChartData(ctx *gin.Context) { 17 | serviceList, _ := AgentService.GetAgentList(ctx, &agentdto.AgentListInput{ 18 | Info: "", 19 | PageNo: 1, 20 | PageSize: 999, 21 | }) 22 | var name []string 23 | var taskNum []int64 24 | for _, s := range serviceList.AgentOutPutItem { 25 | name = append(name, s.ServiceName) 26 | taskNum = append(taskNum, int64(s.TaskNum)) 27 | } 28 | out := &agentdto.BarChartOutPut{ 29 | ServiceName: name, 30 | TaskNum: taskNum, 31 | } 32 | middleware.ResponseSuccess(ctx, out) 33 | } 34 | -------------------------------------------------------------------------------- /dao/roledao/user_group.go: -------------------------------------------------------------------------------- 1 | package roledao 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | // UserGroupDB 用户组表 9 | type UserGroupDB struct { 10 | Id int `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"` 11 | RoleId int `gorm:"column:role_id;type:int(11)" json:"role_id"` 12 | GroupName string `gorm:"column:group_name;type:varchar(20)" json:"group_name"` 13 | Key string `gorm:"column:key;type:varchar(20)" json:"key"` 14 | } 15 | 16 | func (u *UserGroupDB) TableName() string { 17 | return "t_user_group" 18 | } 19 | 20 | func (u *UserGroupDB) Find(ctx *gin.Context, tx *gorm.DB, search *UserGroupDB) (*UserGroupDB, error) { 21 | out := &UserGroupDB{} 22 | return out, tx.WithContext(ctx).Where(search).Find(out).Error 23 | } 24 | 25 | func (u *UserGroupDB) FindList(ctx *gin.Context, tx *gorm.DB, search *UserGroupDB) ([]*UserGroupDB, error) { 26 | var out []*UserGroupDB 27 | return out, tx.WithContext(ctx).Where(search).Find(&out).Error 28 | } 29 | -------------------------------------------------------------------------------- /front/src/locales/lang/en-US.js: -------------------------------------------------------------------------------- 1 | import antdEnUS from 'ant-design-vue/es/locale-provider/en_US' 2 | import momentEU from 'moment/locale/eu' 3 | import global from './en-US/global' 4 | 5 | import menu from './en-US/menu' 6 | import setting from './en-US/setting' 7 | import user from './en-US/user' 8 | 9 | import dashboard from './en-US/dashboard' 10 | import form from './en-US/form' 11 | import result from './en-US/result' 12 | import account from './en-US/account' 13 | 14 | const components = { 15 | antLocale: antdEnUS, 16 | momentName: 'eu', 17 | momentLocale: momentEU 18 | } 19 | 20 | export default { 21 | message: '-', 22 | 23 | 'layouts.usermenu.dialog.title': 'Message', 24 | 'layouts.usermenu.dialog.content': 'Are you sure you would like to logout?', 25 | 'layouts.userLayout.title': 'TBak System A simple and efficient backup system', 26 | ...components, 27 | ...global, 28 | ...menu, 29 | ...setting, 30 | ...user, 31 | ...dashboard, 32 | ...form, 33 | ...result, 34 | ...account 35 | } 36 | -------------------------------------------------------------------------------- /agent/proto/bak/bak.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package go.micro.service.backupAgent; 3 | option go_package="./;bak"; 4 | 5 | service BakService { 6 | rpc StartBak(StartBakInput) returns (BakOneMessage) {} 7 | rpc StopBak(StopBakInput) returns (BakOneMessage) {} 8 | rpc TestBak(StartBakInput) returns (BakOneMessage) {} 9 | rpc StartBakByHost(StartBakByHostInput) returns (BakOneMessage) {} 10 | rpc StopBakByHost(StopBakByHostInput) returns (BakOneMessage) {} 11 | } 12 | 13 | message StartBakInput { 14 | int64 TaskID =1; 15 | string ServiceName =2; 16 | } 17 | 18 | message StartBakByHostInput { 19 | int64 HostID =1; 20 | string ServiceName =2; 21 | } 22 | 23 | 24 | message StopBakInput { 25 | int64 TaskID =1; 26 | string ServiceName =2; 27 | } 28 | 29 | message StopBakByHostInput { 30 | int64 HostID =1; 31 | string ServiceName =2; 32 | } 33 | 34 | 35 | message BakOneMessage { 36 | // @inject_tag: json:"message" 37 | string Message =1; 38 | // @inject_tag: json:"ok" 39 | bool OK =2; 40 | } 41 | -------------------------------------------------------------------------------- /front/src/views/user/RegisterResult.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 41 | 42 | 45 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # This is an example .goreleaser.yml file with some sensible defaults. 2 | # Make sure to check the documentation at https://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | - go generate ./... 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | goos: 13 | - linux 14 | - windows 15 | - darwin 16 | archives: 17 | - replacements: 18 | darwin: Darwin 19 | linux: Linux 20 | windows: Windows 21 | 386: i386 22 | amd64: x86_64 23 | checksum: 24 | name_template: 'checksums.txt' 25 | snapshot: 26 | name_template: "{{ incpatch .Version }}-next" 27 | changelog: 28 | sort: asc 29 | filters: 30 | exclude: 31 | - '^docs:' 32 | - '^test:' 33 | 34 | # modelines, feel free to remove those if you don't want/use them: 35 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 36 | # vim: set ts=2 sw=2 tw=0 fo=cnqoj 37 | -------------------------------------------------------------------------------- /conf/dev/base.toml: -------------------------------------------------------------------------------- 1 | # This is base config 2 | 3 | [base] 4 | debug_mode="debug" 5 | time_location="Asia/Chongqing" 6 | cluster_url="http://127.0.0.1:8880" 7 | 8 | [http] 9 | addr = ":8880" # 监听地址, default ":8700" 10 | read_timeout = 10 # 读取超时时长 11 | write_timeout = 10 # 写入超时时长 12 | max_header_bytes = 20 # 最大的header大小,二进制位长度 13 | 14 | [log] 15 | log_level = "trace" #日志打印最低级别 16 | [log.file_writer] #文件写入配置 17 | on = true 18 | log_path = "./logs/gin-mysqlbak.inf.log" 19 | rotate_log_path = "./logs/gin-mysqlbak.inf.log.%Y%M%D%H" 20 | wf_log_path = "./logs/gin-mysqlbak.wf.log" 21 | rotate_wf_log_path = "./logs/gin-mysqlbak.wf.log.%Y%M%D%H" 22 | [log.console_writer] #工作台输出 23 | on = false 24 | color = false 25 | 26 | [swagger] 27 | title="gin-mysqlbak swagger API" 28 | desc="This is a sample server celler server." 29 | host="127.0.0.1:8880" 30 | base_path="" 31 | 32 | -------------------------------------------------------------------------------- /front/src/views/settings/IconSelectorView.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 37 | -------------------------------------------------------------------------------- /middleware/recovery.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/e421083458/gin_scaffold/public" 7 | "github.com/e421083458/golang_common/lib" 8 | "github.com/gin-gonic/gin" 9 | "github.com/noovertime7/gin-mysqlbak/public/globalError" 10 | "runtime/debug" 11 | ) 12 | 13 | // RecoveryMiddleware RecoveryMiddleware捕获所有panic,并且返回错误信息 14 | func RecoveryMiddleware() gin.HandlerFunc { 15 | return func(c *gin.Context) { 16 | defer func() { 17 | if err := recover(); err != nil { 18 | //先做一下日志记录 19 | fmt.Println(string(debug.Stack())) 20 | public.ComLogNotice(c, "_com_panic", map[string]interface{}{ 21 | "error": fmt.Sprint(err), 22 | "stack": string(debug.Stack()), 23 | }) 24 | 25 | if lib.ConfBase.DebugMode != "debug" { 26 | ResponseError(c, globalError.NewGlobalError(globalError.ServerError, errors.New(""))) 27 | return 28 | } else { 29 | ResponseError(c, globalError.NewGlobalError(globalError.AuthorizationError, errors.New(""))) 30 | return 31 | } 32 | } 33 | }() 34 | c.Next() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /front/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Gin-Mysqlbak-Web](#ant-design-vue-pro) 6 | - [Overview](#overview) 7 | - [环境和依赖](#%E7%8E%AF%E5%A2%83%E5%92%8C%E4%BE%9D%E8%B5%96) 8 | - [项目下载和运行](#%E9%A1%B9%E7%9B%AE%E4%B8%8B%E8%BD%BD%E5%92%8C%E8%BF%90%E8%A1%8C) 9 | - [其他说明](#%E5%85%B6%E4%BB%96%E8%AF%B4%E6%98%8E) 10 | - [浏览器兼容](#%E6%B5%8F%E8%A7%88%E5%99%A8%E5%85%BC%E5%AE%B9) 11 | - [Contributors](#contributors) 12 | 13 | 14 | 15 | 项目下载和运行 16 | ---- 17 | 18 | - 拉取项目代码 19 | ```bash 20 | git clone https://github.com/noovertime7/gin-mysqlbak.git 21 | cd front 22 | ``` 23 | 24 | - 安装依赖 25 | ``` 26 | yarn install 27 | ``` 28 | 29 | - 开发模式运行 30 | ``` 31 | yarn run serve 32 | ``` 33 | 34 | - 编译项目 35 | ``` 36 | yarn run build 37 | ``` 38 | 39 | - Lints and fixes files 40 | ``` 41 | yarn run lint 42 | ``` 43 | -------------------------------------------------------------------------------- /front/src/utils/utils.less: -------------------------------------------------------------------------------- 1 | .textOverflow() { 2 | overflow: hidden; 3 | text-overflow: ellipsis; 4 | word-break: break-all; 5 | white-space: nowrap; 6 | } 7 | 8 | .textOverflowMulti(@line: 3, @bg: #fff) { 9 | position: relative; 10 | max-height: @line * 1.5em; 11 | padding-right: 1em; 12 | margin-right: -1em; 13 | overflow: hidden; 14 | line-height: 1.5em; 15 | text-align: justify; 16 | 17 | &::before { 18 | position: absolute; 19 | right: 14px; 20 | bottom: 0; 21 | padding: 0 1px; 22 | background: @bg; 23 | content: '...'; 24 | } 25 | 26 | &::after { 27 | position: absolute; 28 | right: 14px; 29 | width: 1em; 30 | height: 1em; 31 | margin-top: .2em; 32 | background: white; 33 | content: ''; 34 | } 35 | } 36 | 37 | // mixins for clearfix 38 | // ------------------------ 39 | .clearfix() { 40 | zoom: 1; 41 | 42 | &::before, 43 | &::after { 44 | display: table; 45 | content: ' '; 46 | } 47 | 48 | &::after { 49 | height: 0; 50 | clear: both; 51 | font-size: 0; 52 | visibility: hidden; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /front/src/components/FooterToolbar/FooterToolBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 44 | 45 | 48 | -------------------------------------------------------------------------------- /front/src/config/defaultSettings.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目默认配置项 3 | * primaryColor - 默认主题色, 如果修改颜色不生效,请清理 localStorage 4 | * navTheme - sidebar theme ['dark', 'light'] 两种主题 5 | * colorWeak - 色盲模式 6 | * layout - 整体布局方式 ['sidemenu', 'topmenu'] 两种布局 7 | * fixedHeader - 固定 Header : boolean 8 | * fixSiderbar - 固定左侧菜单栏 : boolean 9 | * contentWidth - 内容区布局: 流式 | 固定 10 | * 11 | * storageOptions: {} - Vue-ls 插件配置项 (localStorage/sessionStorage) 12 | * 13 | */ 14 | 15 | export default { 16 | navTheme: 'dark', // theme for nav menu 17 | primaryColor: '#1890ff', // '#F5222D', // primary color of ant design 18 | layout: 'sidemenu', // nav menu position: `sidemenu` or `topmenu` 19 | contentWidth: 'Fluid', // layout of content: `Fluid` or `Fixed`, only works when layout is topmenu 20 | fixedHeader: false, // sticky header 21 | fixSiderbar: false, // sticky siderbar 22 | colorWeak: false, 23 | menu: { 24 | locale: true 25 | }, 26 | title: 'TBak System', 27 | pwa: false, 28 | iconfontUrl: '', 29 | // production: true 30 | production: process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW !== 'true' 31 | } 32 | -------------------------------------------------------------------------------- /front/src/mock/util.js: -------------------------------------------------------------------------------- 1 | const responseBody = { 2 | message: '', 3 | timestamp: 0, 4 | result: null, 5 | code: 0 6 | } 7 | 8 | export const builder = (data, message, code = 0, headers = {}) => { 9 | responseBody.result = data 10 | if (message !== undefined && message !== null) { 11 | responseBody.message = message 12 | } 13 | if (code !== undefined && code !== 0) { 14 | responseBody.code = code 15 | responseBody._status = code 16 | } 17 | if (headers !== null && typeof headers === 'object' && Object.keys(headers).length > 0) { 18 | responseBody._headers = headers 19 | } 20 | responseBody.timestamp = new Date().getTime() 21 | return responseBody 22 | } 23 | 24 | export const getQueryParameters = (options) => { 25 | const url = options.url 26 | const search = url.split('?')[1] 27 | if (!search) { 28 | return {} 29 | } 30 | return JSON.parse('{"' + decodeURIComponent(search) 31 | .replace(/"/g, '\\"') 32 | .replace(/&/g, '","') 33 | .replace(/=/g, '":"') + '"}') 34 | } 35 | 36 | export const getBody = (options) => { 37 | return options.body && JSON.parse(options.body) 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 noovertime7 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /front/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anan Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /public/rsa.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/base64" 8 | "encoding/pem" 9 | ) 10 | 11 | func RsaDecode(strPlainText string) (string, error) { 12 | plainText, err := base64.StdEncoding.DecodeString(strPlainText) 13 | bytePrivateKey := []byte(PrivateKey) 14 | priBlock, _ := pem.Decode(bytePrivateKey) 15 | priKey, err := x509.ParsePKCS8PrivateKey(priBlock.Bytes) 16 | if err != nil { 17 | return "", err 18 | } 19 | decryptText, err := rsa.DecryptPKCS1v15(rand.Reader, priKey.(*rsa.PrivateKey), plainText) 20 | if err != nil { 21 | return "", err 22 | } 23 | return string(decryptText), nil 24 | } 25 | 26 | func RsaEncode(plain string) (string, error) { 27 | msg := []byte(plain) 28 | pubBlock, _ := pem.Decode([]byte(PublicKey)) 29 | pubKeyValue, err := x509.ParsePKIXPublicKey(pubBlock.Bytes) 30 | if err != nil { 31 | return "", err 32 | } 33 | pub := pubKeyValue.(*rsa.PublicKey) 34 | encryText, err := rsa.EncryptPKCS1v15(rand.Reader, pub, msg) 35 | if err != nil { 36 | return "", err 37 | } 38 | return base64.StdEncoding.EncodeToString(encryText), nil 39 | } 40 | -------------------------------------------------------------------------------- /agent/agentdao/agent_date_info.go: -------------------------------------------------------------------------------- 1 | package agentdao 2 | 3 | import ( 4 | "context" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | // AgentDateInfo 服务根据时间统计完成量 9 | type AgentDateInfo struct { 10 | Id int64 `gorm:"column:id;type:int(11);AUTO_INCREMENT;primary_key" json:"id"` 11 | TaskNum int64 `gorm:"column:task_num;type:int(11);NOT NULL" json:"task_num"` 12 | FinishNum int64 `gorm:"column:finish_num;type:int(11);NOT NULL" json:"finish_num"` 13 | CurrentTime string `gorm:"column:current_time;type:datetime;NOT NULL" json:"current_time"` 14 | } 15 | 16 | func (a *AgentDateInfo) TableName() string { 17 | return "agent_date_info" 18 | } 19 | 20 | func (a *AgentDateInfo) Save(ctx context.Context, tx *gorm.DB) error { 21 | return tx.WithContext(ctx).Save(a).Error 22 | } 23 | 24 | func (a *AgentDateInfo) Updates(ctx context.Context, tx *gorm.DB) error { 25 | return tx.WithContext(ctx).Where("id = ?", a.Id).Updates(a).Error 26 | } 27 | 28 | func (a *AgentDateInfo) Find(ctx context.Context, tx *gorm.DB, search *AgentDateInfo) (*AgentDateInfo, error) { 29 | out := &AgentDateInfo{} 30 | return out, tx.WithContext(ctx).Where(search).Find(out).Error 31 | } 32 | -------------------------------------------------------------------------------- /front/src/components/_util/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * components util 3 | */ 4 | 5 | /** 6 | * 清理空值,对象 7 | * @param children 8 | * @returns {*[]} 9 | */ 10 | export function filterEmpty (children = []) { 11 | return children.filter(c => c.tag || (c.text && c.text.trim() !== '')) 12 | } 13 | 14 | /** 15 | * 获取字符串长度,英文字符 长度1,中文字符长度2 16 | * @param {*} str 17 | */ 18 | export const getStrFullLength = (str = '') => 19 | str.split('').reduce((pre, cur) => { 20 | const charCode = cur.charCodeAt(0) 21 | if (charCode >= 0 && charCode <= 128) { 22 | return pre + 1 23 | } 24 | return pre + 2 25 | }, 0) 26 | 27 | /** 28 | * 截取字符串,根据 maxLength 截取后返回 29 | * @param {*} str 30 | * @param {*} maxLength 31 | */ 32 | export const cutStrByFullLength = (str = '', maxLength) => { 33 | let showLength = 0 34 | return str.split('').reduce((pre, cur) => { 35 | const charCode = cur.charCodeAt(0) 36 | if (charCode >= 0 && charCode <= 128) { 37 | showLength += 1 38 | } else { 39 | showLength += 2 40 | } 41 | if (showLength <= maxLength) { 42 | return pre + cur 43 | } 44 | return pre 45 | }, '') 46 | } 47 | -------------------------------------------------------------------------------- /front/.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve(Bug 反馈) 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug (描述 Bug)** 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | 15 | 16 | **To Reproduce (重现步骤)** 17 | Steps to reproduce the behavior: 18 | 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | 24 | 25 | 26 | **Expected behavior(你期待的是什么?)** 27 | A clear and concise description of what you expected to happen. 28 | 29 | 30 | 31 | **Screenshots(截图)** 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | 35 | 36 | **Desktop (please complete the following information):** 37 | 38 | - OS: [e.g. iOS] 39 | - Browser [e.g. chrome, safari] 40 | - Version [e.g. 22] 41 | 42 | 43 | 44 | **Smartphone (please complete the following information):** 45 | 46 | - Device: [e.g. iPhone6] 47 | - OS: [e.g. iOS8.1] 48 | - Browser [e.g. stock browser, safari] 49 | - Version [e.g. 22] 50 | 51 | 52 | 53 | **Additional context(附加信息)** 54 | Add any other context about the problem here. -------------------------------------------------------------------------------- /public/alioss/alioss.go: -------------------------------------------------------------------------------- 1 | package alioss 2 | 3 | import ( 4 | "github.com/noovertime7/mysqlbak/pkg/log" 5 | "strings" 6 | 7 | "github.com/aliyun/aliyun-oss-go-sdk/oss" 8 | ) 9 | 10 | func AliOssUploadFile(filename, Endpoint, Accesskey, Secretkey, BucketName, Directory string) error { 11 | // 创建OSSClient实例。 12 | // yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。 13 | // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。 14 | 15 | client, err := oss.New(Endpoint, Accesskey, Secretkey) 16 | if err != nil { 17 | log.Logger.Error("Error:", err) 18 | return err 19 | } 20 | 21 | // 填写存储空间名称,例如examplebucket。 22 | bucket, err := client.Bucket(BucketName) 23 | if err != nil { 24 | log.Logger.Error("Error:", err) 25 | return err 26 | } 27 | 28 | // 依次填写Object的完整路径(例如exampledir/exampleobject.txt)和本地文件的完整路径(例如D:\\localpath\\examplefile.txt)。 29 | file := strings.Split(filename, "/")[len(strings.Split(filename, "/"))-1] //需要处理一下拿到文件名 30 | err = bucket.PutObjectFromFile(Directory+file, filename) 31 | if err != nil { 32 | log.Logger.Error("Error:", err) 33 | return err 34 | } 35 | return nil 36 | } 37 | -------------------------------------------------------------------------------- /test/grpc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/micro/go-micro/v2" 7 | "github.com/micro/go-micro/v2/client" 8 | "github.com/noovertime7/gin-mysqlbak/agent/proto/bakhistory" 9 | "github.com/noovertime7/mysqlbak/pkg/log" 10 | ) 11 | 12 | const ( 13 | address = "localhost:39800" 14 | ) 15 | 16 | func main() { 17 | //conn, err := grpc.Dial(address, grpc.WithInsecure()) //建立客户端和服务器之间的链接 18 | //if err != nil { 19 | // log.Fatalf("did not connect %v", err) 20 | //} 21 | //defer conn.Close() 22 | service := micro.NewService() 23 | service.Init() 24 | var ops client.CallOption = func(options *client.CallOptions) { 25 | options.Address = []string{"127.0.0.1:30000"} 26 | } 27 | h := bakhistory.NewHistoryService("test5.local", service.Client()) 28 | historyListInput := &bakhistory.HistoryListInput{ 29 | Info: "", 30 | PageNo: 1, 31 | PageSize: 10, 32 | Sort: "", 33 | } 34 | 35 | for i := 0; i < 10; i++ { 36 | data, err := h.GetHistoryList(context.Background(), historyListInput, ops) 37 | if err != nil { 38 | log.Logger.Error("agent获取历史记录列表失败", err) 39 | return 40 | } 41 | fmt.Println(data) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /front/src/components/TagSelect/TagSelectOption.jsx: -------------------------------------------------------------------------------- 1 | import { Tag } from 'ant-design-vue' 2 | const { CheckableTag } = Tag 3 | 4 | export default { 5 | name: 'TagSelectOption', 6 | props: { 7 | prefixCls: { 8 | type: String, 9 | default: 'ant-pro-tag-select-option' 10 | }, 11 | value: { 12 | type: [String, Number, Object], 13 | default: '' 14 | }, 15 | checked: { 16 | type: Boolean, 17 | default: false 18 | } 19 | }, 20 | data () { 21 | return { 22 | localChecked: this.checked || false 23 | } 24 | }, 25 | watch: { 26 | 'checked' (val) { 27 | this.localChecked = val 28 | }, 29 | '$parent.items': { 30 | handler: function (val) { 31 | this.value && val.hasOwnProperty(this.value) && (this.localChecked = val[this.value]) 32 | }, 33 | deep: true 34 | } 35 | }, 36 | render () { 37 | const { $slots, value } = this 38 | const onChange = (checked) => { 39 | this.$emit('change', { value, checked }) 40 | } 41 | return ( 42 | {$slots.default} 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /front/src/views/list/search/components/CardInfo.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 29 | 30 | 59 | -------------------------------------------------------------------------------- /dao/roledao/role.go: -------------------------------------------------------------------------------- 1 | package roledao 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm" 6 | "time" 7 | ) 8 | 9 | // RoleDB TRole 角色表 10 | type RoleDB struct { 11 | Id int `gorm:"column:id;type:int(11);primary_key;AUTO_INCREMENT" json:"id"` 12 | Name string `gorm:"column:name;type:varchar(20)" json:"name"` 13 | RoleId string `gorm:"column:role_id;type:varchar(20)" json:"role_id"` 14 | Describe string `gorm:"column:describe;type:varchar(20)" json:"describe"` 15 | CreateAt time.Time `gorm:"column:create_at;type:datetime" json:"create_at"` 16 | CreateUser string `gorm:"column:create_user;type:varchar(20)" json:"create_user"` 17 | IsDeleted int `gorm:"column:is_deleted;type:int(11)" json:"is_deleted"` 18 | PermissionId int `gorm:"column:permission_id;type:int(11)" json:"permission_id"` 19 | } 20 | 21 | func (r *RoleDB) TableName() string { 22 | return "t_role" 23 | } 24 | 25 | func (r *RoleDB) Find(c *gin.Context, tx *gorm.DB, search *RoleDB) (*RoleDB, error) { 26 | out := &RoleDB{} 27 | if err := tx.WithContext(c).Where(search).Find(out).Error; err != nil { 28 | return nil, err 29 | } 30 | return out, nil 31 | } 32 | -------------------------------------------------------------------------------- /front/src/views/list/QueryList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 51 | -------------------------------------------------------------------------------- /front/src/api/agent-host.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | import { Encrypt } from '@/utils/security' 3 | 4 | export function GetAgentHostList (query) { 5 | return request({ 6 | url: '/agent/hostlist', 7 | method: 'get', 8 | params: query 9 | }) 10 | } 11 | 12 | export function TestAgentHost (query) { 13 | return request({ 14 | url: '/agent/host_test', 15 | method: 'get', 16 | params: query 17 | }) 18 | } 19 | 20 | export function GetHostNames (query) { 21 | return request({ 22 | url: '/agent/host_names', 23 | method: 'get', 24 | params: query 25 | }) 26 | } 27 | 28 | export function CreateAgentHost (data) { 29 | data.password = Encrypt(data.password) 30 | return request({ 31 | url: '/agent/hostadd', 32 | method: 'post', 33 | data 34 | }) 35 | } 36 | 37 | export function UpdateAgentHost (query) { 38 | query.password = Encrypt(query.password) 39 | return request({ 40 | url: '/agent/hostupdate', 41 | method: 'put', 42 | params: query 43 | }) 44 | } 45 | 46 | export function DeleteAgentHost (query) { 47 | return request({ 48 | url: '/agent/hostdelete', 49 | method: 'delete', 50 | params: query 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /front/src/components/Charts/MiniArea.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 53 | 54 | 57 | -------------------------------------------------------------------------------- /front/src/components/Charts/MiniBar.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 54 | 55 | 58 | -------------------------------------------------------------------------------- /front/src/locales/lang/en-US/result/success.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'result.success.title': 'Submission Success', 3 | 'result.success.description': 4 | 'The submission results page is used to feed back the results of a series of operational tasks. If it is a simple operation, use the Message global prompt feedback. This text area can show a simple supplementary explanation. If there is a similar requirement for displaying “documents”, the following gray area can present more complicated content.', 5 | 'result.success.operate-title': 'Project Name', 6 | 'result.success.operate-id': 'Project ID', 7 | 'result.success.principal': 'Principal', 8 | 'result.success.operate-time': 'Effective time', 9 | 'result.success.step1-title': 'Create project', 10 | 'result.success.step1-operator': 'Qu Lili', 11 | 'result.success.step2-title': 'Departmental preliminary review', 12 | 'result.success.step2-operator': 'Zhou Maomao', 13 | 'result.success.step2-extra': 'Urge', 14 | 'result.success.step3-title': 'Financial review', 15 | 'result.success.step4-title': 'Finish', 16 | 'result.success.btn-return': 'Back List', 17 | 'result.success.btn-project': 'View Project', 18 | 'result.success.btn-print': 'Print' 19 | } 20 | -------------------------------------------------------------------------------- /front/src/views/cluster/task/model.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 43 | 44 | 47 | -------------------------------------------------------------------------------- /front/src/core/directives/action.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import store from '@/store' 3 | 4 | /** 5 | * Action 权限指令 6 | * 指令用法: 7 | * - 在需要控制 action 级别权限的组件上使用 v-action:[method] , 如下: 8 | * 添加用户 9 | * 删除用户 10 | * 修改 11 | * 12 | * - 当前用户没有权限时,组件上使用了该指令则会被隐藏 13 | * - 当后台权限跟 pro 提供的模式不同时,只需要针对这里的权限过滤进行修改即可 14 | * 15 | * @see https://github.com/vueComponent/ant-design-vue-pro/pull/53 16 | */ 17 | const action = Vue.directive('action', { 18 | inserted: function (el, binding, vnode) { 19 | const actionName = binding.arg 20 | const roles = store.getters.roles 21 | const elVal = vnode.context.$route.meta.permission 22 | const permissionId = Object.prototype.toString.call(elVal) === '[object String]' && [elVal] || elVal 23 | roles.permissions.forEach(p => { 24 | if (!permissionId.includes(p.permissionId)) { 25 | return 26 | } 27 | if (p.actionList && !p.actionList.includes(actionName)) { 28 | el.parentNode && el.parentNode.removeChild(el) || (el.style.display = 'none') 29 | } 30 | }) 31 | } 32 | }) 33 | 34 | export default action 35 | -------------------------------------------------------------------------------- /front/src/views/list/components/Info.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 58 | -------------------------------------------------------------------------------- /front/src/views/local/app/components/Info.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 58 | -------------------------------------------------------------------------------- /front/src/views/cluster/service/components/Info.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 58 | -------------------------------------------------------------------------------- /front/src/views/cluster/history/mysql/components/Info.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 58 | -------------------------------------------------------------------------------- /front/src/views/local/history/mysql/components/Info.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 58 | -------------------------------------------------------------------------------- /front/src/components/Charts/ClusterFinishNumChart.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 48 | 49 | 52 | -------------------------------------------------------------------------------- /front/src/views/cluster/history/model.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 43 | 44 | 47 | -------------------------------------------------------------------------------- /front/src/components/SettingDrawer/settingConfig.js: -------------------------------------------------------------------------------- 1 | import message from 'ant-design-vue/es/message' 2 | // import defaultSettings from '../defaultSettings'; 3 | import themeColor from './themeColor.js' 4 | 5 | // let lessNodesAppended 6 | const colorList = [ 7 | { 8 | key: '薄暮', color: '#F5222D' 9 | }, 10 | { 11 | key: '火山', color: '#FA541C' 12 | }, 13 | { 14 | key: '日暮', color: '#FAAD14' 15 | }, 16 | { 17 | key: '明青', color: '#13C2C2' 18 | }, 19 | { 20 | key: '极光绿', color: '#52C41A' 21 | }, 22 | { 23 | key: '拂晓蓝(默认)', color: '#1890FF' 24 | }, 25 | { 26 | key: '极客蓝', color: '#2F54EB' 27 | }, 28 | { 29 | key: '酱紫', color: '#722ED1' 30 | } 31 | ] 32 | 33 | const updateTheme = newPrimaryColor => { 34 | const hideMessage = message.loading('正在切换主题!', 0) 35 | themeColor.changeColor(newPrimaryColor).finally(() => { 36 | setTimeout(() => { 37 | hideMessage() 38 | }, 10) 39 | }) 40 | } 41 | 42 | const updateColorWeak = colorWeak => { 43 | // document.body.className = colorWeak ? 'colorWeak' : ''; 44 | const app = document.body.querySelector('#app') 45 | colorWeak ? app.classList.add('colorWeak') : app.classList.remove('colorWeak') 46 | } 47 | 48 | export { updateTheme, colorList, updateColorWeak } 49 | -------------------------------------------------------------------------------- /server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "github.com/noovertime7/gin-mysqlbak/conf" 5 | "github.com/noovertime7/gin-mysqlbak/core/job" 6 | "github.com/noovertime7/gin-mysqlbak/job/system" 7 | "github.com/noovertime7/gin-mysqlbak/public" 8 | "github.com/noovertime7/gin-mysqlbak/services" 9 | "github.com/noovertime7/mysqlbak/pkg/log" 10 | ) 11 | 12 | // Start 启动服务有关 13 | func Start() { 14 | //打印logo 15 | public.PrintLogo() 16 | go func() { 17 | if err := startSyncClusterTask(); err != nil { 18 | log.Logger.Warning("集群同步任务列表失败,会导致任务总览显示异常", err) 19 | } 20 | }() 21 | } 22 | 23 | func Stop() { 24 | services.StopAllUpTask() 25 | } 26 | 27 | // startSyncClusterTask 同步集群任务 28 | func startSyncClusterTask() error { 29 | ctx := GetGlobalContext() 30 | //默认同步时间周期为30分钟一次 31 | period := conf.GetStringConf("cluster", "clusterSyncPeriod") 32 | if period == "" { 33 | period = "00 30 * * *" 34 | } 35 | factory := job.GetJobFactory() 36 | taskSync := system.NewTaskSyncJob(ctx, period) 37 | //log.Logger.Infof("启动集群任务同步定时器,当前同步周期%s", period) 38 | datesync := system.NewDateNumInfoSync(ctx, period) 39 | factory.Register(datesync, job.JobType(public.DateNumInfoJob)) 40 | factory.Register(taskSync, job.JobType(public.TaskSyncJob)) 41 | return factory.Start() 42 | } 43 | -------------------------------------------------------------------------------- /front/src/views/local/task/taskList.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 52 | -------------------------------------------------------------------------------- /front/src/components/Editor/WangEditor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 50 | 51 | 58 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/setting.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': '整体风格设置', 3 | 'app.setting.pagestyle.light': '亮色菜单风格', 4 | 'app.setting.pagestyle.dark': '暗色菜单风格', 5 | 'app.setting.pagestyle.realdark': '暗黑模式', 6 | 'app.setting.themecolor': '主题色', 7 | 'app.setting.navigationmode': '导航模式', 8 | 'app.setting.content-width': '内容区域宽度', 9 | 'app.setting.fixedheader': '固定 Header', 10 | 'app.setting.fixedsidebar': '固定侧边栏', 11 | 'app.setting.sidemenu': '侧边菜单布局', 12 | 'app.setting.topmenu': '顶部菜单布局', 13 | 'app.setting.content-width.fixed': 'Fixed', 14 | 'app.setting.content-width.fluid': 'Fluid', 15 | 'app.setting.othersettings': '其他设置', 16 | 'app.setting.weakmode': '色弱模式', 17 | 'app.setting.copy': '拷贝设置', 18 | 'app.setting.loading': '加载主题中', 19 | 'app.setting.copyinfo': '拷贝设置成功 src/config/defaultSettings.js', 20 | 'app.setting.production.hint': '配置栏只在开发环境用于预览,生产环境不会展现,请拷贝后手动修改配置文件', 21 | 'app.setting.themecolor.daybreak': '拂晓蓝', 22 | 'app.setting.themecolor.dust': '薄暮', 23 | 'app.setting.themecolor.volcano': '火山', 24 | 'app.setting.themecolor.sunset': '日暮', 25 | 'app.setting.themecolor.cyan': '明青', 26 | 'app.setting.themecolor.green': '极光绿', 27 | 'app.setting.themecolor.geekblue': '极客蓝', 28 | 'app.setting.themecolor.purple': '酱紫' 29 | } 30 | -------------------------------------------------------------------------------- /front/src/views/cluster/task/elastic/ClusterElasticTask.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 51 | -------------------------------------------------------------------------------- /front/src/components/AvatarList/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list"; 4 | @avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item"; 5 | 6 | .@{avatar-list-prefix-cls} { 7 | display: inline-block; 8 | 9 | ul { 10 | display: inline-block; 11 | padding: 0; 12 | margin: 0 0 0 8px; 13 | font-size: 0; 14 | list-style: none; 15 | } 16 | } 17 | 18 | .@{avatar-list-item-prefix-cls} { 19 | display: inline-block; 20 | width: @avatar-size-base; 21 | height: @avatar-size-base; 22 | margin-left: -8px; 23 | font-size: @font-size-base; 24 | 25 | :global { 26 | .ant-avatar { 27 | cursor: pointer; 28 | border: 1px solid #fff; 29 | } 30 | } 31 | 32 | &.large { 33 | width: @avatar-size-lg; 34 | height: @avatar-size-lg; 35 | } 36 | 37 | &.small { 38 | width: @avatar-size-sm; 39 | height: @avatar-size-sm; 40 | } 41 | 42 | &.mini { 43 | width: 20px; 44 | height: 20px; 45 | 46 | :global { 47 | .ant-avatar { 48 | width: 20px; 49 | height: 20px; 50 | line-height: 20px; 51 | 52 | .ant-avatar-string { 53 | font-size: 12px; 54 | line-height: 18px; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /front/src/components/Charts/TransferBar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 65 | -------------------------------------------------------------------------------- /front/src/components/Charts/ClusterTaskBar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 59 | -------------------------------------------------------------------------------- /front/src/components/Charts/ClusterFinishBar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 59 | -------------------------------------------------------------------------------- /front/docs/add-page-loading-animate.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [为首屏增加 加载动画](#%E4%B8%BA%E9%A6%96%E5%B1%8F%E5%A2%9E%E5%8A%A0-%E5%8A%A0%E8%BD%BD%E5%8A%A8%E7%94%BB) 6 | - [需求](#%E9%9C%80%E6%B1%82) 7 | - [实现方案](#%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%A1%88) 8 | - [写在最后](#%E5%86%99%E5%9C%A8%E6%9C%80%E5%90%8E) 9 | 10 | 11 | 12 | 为首屏增加 加载动画 13 | ==== 14 | 15 | 16 | 17 | ## 需求 18 | 19 | > 为了缓解用户第一次访问时,加载 JS 过大所导致用户等待白屏时间过长导致的用户体验不好,进行的一个优化动效。 20 | 21 | 22 | 23 | ## 实现方案 24 | 25 | 1. 将 动画加载 dom 元素放在 #app 内,Vue 生命周期开始时,会自动清掉 #app 下的所有元素。 26 | 2. 将 动画加载 dom 元素放在 body 下,Vue 生命周期开始时 App.vue (created, mounted) 调用 `@/utils/utll` 下的 removeLoadingAnimate(#id, timeout) 则会移除加载动画 27 | 28 | 最后一步: 29 | ​ 将样式插入到 `public/index.html` 文件的 `` 最好写成内联 `` 30 | 31 | 32 | 33 | ---- 34 | 35 | 目前提供有两个样式,均在 `public/loading` 文件夹内。且 pro 已经默认使用了一套 loading 动画方案,可以直接参考 `public/index.html` 36 | 37 | 38 | ## 写在最后 39 | 40 | 目前 pro 有页面 overflow 显示出浏览器滚动条时,页面会抖动一下的问题。 41 | 42 | 欢迎各位提供能解决的方案和实现 demo。如果在条件允许的情况下,建议请直接使用 pro 进行改造,也欢迎直接 PR 到 pro 的仓库 43 | -------------------------------------------------------------------------------- /front/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /front/src/components/NumberInfo/index.less: -------------------------------------------------------------------------------- 1 | @import '../index'; 2 | 3 | @numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info"; 4 | 5 | .@{numberInfo-prefix-cls} { 6 | .ant-pro-number-info-subtitle { 7 | height: 22px; 8 | overflow: hidden; 9 | font-size: @font-size-base; 10 | line-height: 22px; 11 | color: @text-color-secondary; 12 | text-overflow: ellipsis; 13 | word-break: break-all; 14 | white-space: nowrap; 15 | } 16 | 17 | .number-info-value { 18 | margin-top: 4px; 19 | overflow: hidden; 20 | font-size: 0; 21 | text-overflow: ellipsis; 22 | word-break: break-all; 23 | white-space: nowrap; 24 | 25 | & > span { 26 | display: inline-block; 27 | height: 32px; 28 | margin-right: 32px; 29 | font-size: 24px; 30 | line-height: 32px; 31 | color: @heading-color; 32 | } 33 | 34 | .sub-total { 35 | margin-right: 0; 36 | font-size: @font-size-lg; 37 | color: @text-color-secondary; 38 | vertical-align: top; 39 | 40 | i { 41 | margin-left: 4px; 42 | font-size: 12px; 43 | transform: scale(.82); 44 | } 45 | 46 | .anticon-caret-up { 47 | color: @red-6; 48 | } 49 | 50 | .anticon-caret-down { 51 | color: @green-6; 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /front/src/main.js: -------------------------------------------------------------------------------- 1 | // with polyfills 2 | import 'core-js/stable' 3 | import 'regenerator-runtime/runtime' 4 | 5 | import Vue from 'vue' 6 | import App from './App.vue' 7 | import router from './router' 8 | import store from './store/' 9 | import i18n from './locales' 10 | import { VueAxios } from './utils/request' 11 | import ProLayout, { PageHeaderWrapper } from '@ant-design-vue/pro-layout' 12 | import themePluginConfig from '../config/themePluginConfig' 13 | 14 | // mock 15 | // WARNING: `mockjs` NOT SUPPORT `IE` PLEASE DO NOT USE IN `production` ENV. 16 | 17 | import bootstrap from './core/bootstrap' 18 | import './core/lazy_use' // use lazy load components 19 | import './permission' // permission control 20 | import './utils/filter' // global filter 21 | import './global.less' // global style 22 | 23 | Vue.config.productionTip = false 24 | 25 | // mount axios to `Vue.$http` and `this.$http` 26 | Vue.use(VueAxios) 27 | // use pro-layout components 28 | Vue.component('pro-layout', ProLayout) 29 | Vue.component('page-container', PageHeaderWrapper) 30 | Vue.component('page-header-wrapper', PageHeaderWrapper) 31 | 32 | window.umi_plugin_ant_themeVar = themePluginConfig.theme 33 | 34 | new Vue({ 35 | router, 36 | store, 37 | i18n, 38 | // init localstorage, vuex, Logo message 39 | created: bootstrap, 40 | render: h => h(App) 41 | }).$mount('#app') 42 | -------------------------------------------------------------------------------- /middleware/jwt.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/noovertime7/gin-mysqlbak/public" 6 | "github.com/noovertime7/gin-mysqlbak/public/globalError" 7 | "github.com/pkg/errors" 8 | ) 9 | 10 | // JWTAuth jwt认证函数 11 | func JWTAuth() gin.HandlerFunc { 12 | return func(context *gin.Context) { 13 | if len(context.Request.URL.String()) >= 18 && context.Request.Method == "POST" && context.Request.URL.String()[0:18] == "/admin_login/login" { 14 | context.Next() 15 | return 16 | } 17 | // 处理验证逻辑 18 | token := context.Request.Header.Get("Access-Token") 19 | if token == "" { 20 | ResponseError(context, globalError.NewGlobalError(globalError.AuthorizationError, errors.New("UnAuthorization"))) 21 | context.Abort() 22 | return 23 | } 24 | // 解析token内容 25 | claims, err := public.JWTToken.ParseToken(token) 26 | if err != nil { 27 | // token过期错误 28 | if err.Error() == "TokenExpired" { 29 | ResponseError(context, globalError.NewGlobalError(globalError.AuthorizationError, err)) 30 | context.Abort() 31 | return 32 | } 33 | // 解析其他错误 34 | ResponseError(context, globalError.NewGlobalError(globalError.AuthorizationError, err)) 35 | context.Abort() 36 | return 37 | } 38 | context.Set("uid", claims.Uid) 39 | context.Set("token", token) 40 | context.Next() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /dao/job_history.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "context" 5 | "database/sql" 6 | "gorm.io/gorm" 7 | "time" 8 | ) 9 | 10 | type JobHistory struct { 11 | ID int `gorm:"column:id;type:int(11);AUTO_INCREMENT;primary_key" json:"id"` 12 | JobType int `gorm:"column:job_type;type:int(11);NOT NULL" json:"job_type"` 13 | JobCycle string `gorm:"column:job_cycle;type:varchar(20);NOT NULL" json:"job_cycle"` 14 | Affected int `gorm:"column:affected;type:int(11)" json:"affected"` 15 | Status sql.NullInt64 `gorm:"column:status;type:int(11);NOT NULL" json:"status"` 16 | Message string `gorm:"column:message;type:varchar(255)" json:"message"` 17 | UpdateTime time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` 18 | } 19 | 20 | func (j *JobHistory) TableName() string { 21 | return "job_history" 22 | } 23 | 24 | func (j *JobHistory) Save(c context.Context, tx *gorm.DB) error { 25 | return tx.WithContext(c).Save(j).Error 26 | } 27 | 28 | func (j *JobHistory) Find(c context.Context, tx *gorm.DB, search *JobHistory) (*JobHistory, error) { 29 | out := &JobHistory{} 30 | return out, tx.WithContext(c).Where(search).Find(&out).Error 31 | } 32 | 33 | func (j *JobHistory) Updates(c context.Context, tx *gorm.DB) error { 34 | return tx.WithContext(c).Where("id = ?", j.ID).Updates(j).Error 35 | } 36 | -------------------------------------------------------------------------------- /front/src/components/Trend/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Trend 趋势标记](#trend-%E8%B6%8B%E5%8A%BF%E6%A0%87%E8%AE%B0) 6 | - [代码演示 demo](#%E4%BB%A3%E7%A0%81%E6%BC%94%E7%A4%BA--demo) 7 | - [API](#api) 8 | 9 | 10 | 11 | # Trend 趋势标记 12 | 13 | 趋势符号,标记上升和下降趋势。通常用绿色代表“好”,红色代表“不好”,股票涨跌场景除外。 14 | 15 | 16 | 17 | 引用方式: 18 | 19 | ```javascript 20 | import Trend from '@/components/Trend' 21 | 22 | export default { 23 | components: { 24 | Trend 25 | } 26 | } 27 | ``` 28 | 29 | 30 | 31 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 32 | 33 | ```html 34 | 5% 35 | ``` 36 | 或 37 | ```html 38 | 39 | 工资 40 | 5% 41 | 42 | ``` 43 | 或 44 | ```html 45 | 5% 46 | ``` 47 | 48 | 49 | ## API 50 | 51 | | 参数 | 说明 | 类型 | 默认值 | 52 | |----------|------------------------------------------|-------------|-------| 53 | | flag | 上升下降标识:`up|down` | string | - | 54 | | reverseColor | 颜色反转 | Boolean | false | 55 | 56 | -------------------------------------------------------------------------------- /front/src/components/Charts/MiniSmoothArea.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 55 | 56 | 59 | -------------------------------------------------------------------------------- /front/src/components/Ellipsis/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [Ellipsis 文本自动省略号](#ellipsis-%E6%96%87%E6%9C%AC%E8%87%AA%E5%8A%A8%E7%9C%81%E7%95%A5%E5%8F%B7) 6 | - [代码演示 demo](#%E4%BB%A3%E7%A0%81%E6%BC%94%E7%A4%BA--demo) 7 | - [API](#api) 8 | 9 | 10 | 11 | # Ellipsis 文本自动省略号 12 | 13 | 文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。 14 | 15 | 16 | 17 | 引用方式: 18 | 19 | ```javascript 20 | import Ellipsis from '@/components/Ellipsis' 21 | 22 | export default { 23 | components: { 24 | Ellipsis 25 | } 26 | } 27 | ``` 28 | 29 | 30 | 31 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 32 | 33 | ```html 34 | 35 | There were injuries alleged in three cases in 2015, and a 36 | fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall. 37 | 38 | ``` 39 | 40 | 41 | 42 | ## API 43 | 44 | 45 | 参数 | 说明 | 类型 | 默认值 46 | ----|------|-----|------ 47 | tooltip | 移动到文本展示完整内容的提示 | boolean | - 48 | length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | - -------------------------------------------------------------------------------- /front/src/api/manage.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | const api = { 4 | user: '/user', 5 | role: '/role', 6 | service: '/service', 7 | permission: '/permission', 8 | permissionNoPager: '/permission/no-pager', 9 | orgTree: '/org/tree' 10 | } 11 | 12 | export default api 13 | 14 | export function getRoleList (parameter) { 15 | return request({ 16 | url: api.role, 17 | method: 'get', 18 | params: parameter 19 | }) 20 | } 21 | 22 | export function getServiceList (parameter) { 23 | return request({ 24 | url: api.service, 25 | method: 'get', 26 | params: parameter 27 | }) 28 | } 29 | 30 | export function getPermissions (parameter) { 31 | return request({ 32 | url: api.permissionNoPager, 33 | method: 'get', 34 | params: parameter 35 | }) 36 | } 37 | 38 | export function getOrgTree (parameter) { 39 | return request({ 40 | url: api.orgTree, 41 | method: 'get', 42 | params: parameter 43 | }) 44 | } 45 | 46 | // id == 0 add post 47 | // id != 0 update put 48 | export function saveService (parameter) { 49 | return request({ 50 | url: api.service, 51 | method: parameter.id === 0 ? 'post' : 'put', 52 | data: parameter 53 | }) 54 | } 55 | 56 | export function saveSub (sub) { 57 | return request({ 58 | url: '/sub', 59 | method: sub.id === 0 ? 'post' : 'put', 60 | data: sub 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /front/src/components/IconSelector/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [IconSelector](#iconselector) 6 | - [使用方式](#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F) 7 | - [事件](#%E4%BA%8B%E4%BB%B6) 8 | 9 | 10 | 11 | IconSelector 12 | ==== 13 | 14 | > 图标选择组件,常用于为某一个数据设定一个图标时使用 15 | > eg: 设定菜单列表时,为每个菜单设定一个图标 16 | 17 | 该组件由 [@Saraka](https://github.com/saraka-tsukai) 封装 18 | 19 | 20 | 21 | ### 使用方式 22 | 23 | ```vue 24 | 29 | 30 | 49 | ``` 50 | 51 | 52 | 53 | ### 事件 54 | 55 | 56 | | 名称 | 说明 | 类型 | 默认值 | 57 | | ------ | -------------------------- | ------ | ------ | 58 | | change | 当改变了 `icon` 选中项触发 | String | - | 59 | -------------------------------------------------------------------------------- /front/src/components/NumberInfo/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [NumberInfo 数据文本](#numberinfo-%E6%95%B0%E6%8D%AE%E6%96%87%E6%9C%AC) 6 | - [代码演示 demo](#%E4%BB%A3%E7%A0%81%E6%BC%94%E7%A4%BA--demo) 7 | - [API](#api) 8 | 9 | 10 | 11 | # NumberInfo 数据文本 12 | 13 | 常用在数据卡片中,用于突出展示某个业务数据。 14 | 15 | 16 | 17 | 引用方式: 18 | 19 | ```javascript 20 | import NumberInfo from '@/components/NumberInfo' 21 | 22 | export default { 23 | components: { 24 | NumberInfo 25 | } 26 | } 27 | ``` 28 | 29 | 30 | 31 | ## 代码演示 [demo](https://pro.loacg.com/test/home) 32 | 33 | ```html 34 | 39 | ``` 40 | 41 | 42 | 43 | ## API 44 | 45 | 参数 | 说明 | 类型 | 默认值 46 | ----|------|-----|------ 47 | title | 标题 | ReactNode\|string | - 48 | subTitle | 子标题 | ReactNode\|string | - 49 | total | 总量 | ReactNode\|string | - 50 | subTotal | 子总量 | ReactNode\|string | - 51 | status | 增加状态 | 'up \| down' | - 52 | theme | 状态样式 | string | 'light' 53 | gap | 设置数字和描述之间的间距(像素)| number | 8 54 | -------------------------------------------------------------------------------- /front/src/views/cluster/history/elastic/components/Info.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 32 | 33 | 65 | -------------------------------------------------------------------------------- /kubernetes/agent/agent-conf.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | config.ini: > 4 | [base] 5 | serviceName = 公司环境 6 | version = 1.0 7 | addr = ":39010" # 监听地址 8 | content = "公司环境" 9 | photoUrl = "https://lmg.jj20.com/up/allimg/tp09/210F2130512J47-0-lp.jpg" 10 | [register] 11 | registerAddr = "192.168.1.60:39010" ## agent地址,用于服务端调用,向server上报自己的地址 12 | registrationCycle = 10 ## 注册周期,单位分钟 13 | content = "公司环境" ## 备注 14 | registerUrl = http://192.168.1.51:19008/prod-api/public/register ## server前端地址,用于向服务发起注册 15 | deregisterUrl = http://192.168.1.51:19008/prod-api/public/deregister ##server前端地址,用于向服务发起注册 16 | [mysql] 17 | autoInit = true ## 是否自动初始化sql,首次安装请打开,需要手动创建数据库 18 | host = mysql.video 19 | port = 3306 20 | user = root 21 | password = tsit@123 22 | dbname = backup-agent 23 | [EsBackup] 24 | RepositoryName = test ## es备份使用的默认仓库名 25 | RepositoryDir = "/usr/share/elasticsearch/bakfile" ## 备份快照存储文件夹,需要手动创建 path.repo: ["/usr/share/elasticsearch/bakfile"] 26 | [dingProxyAgent] 27 | enable = false ## 用于外网环境下发送钉钉消息 28 | addr = 172.20.96.56:39999 29 | title = "公司环境" 30 | content = "公司环境" 31 | [jaeger] 32 | enable = false 33 | addr = 10.20.110.51:6831 34 | kind: ConfigMap 35 | metadata: 36 | name: mysqlbak-agent-conf 37 | namespace: mysqlbak 38 | resourceVersion: '92033238' 39 | 40 | -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | definitions: 2 | dto.DemoInput: 3 | properties: 4 | age: 5 | example: 20 6 | type: integer 7 | name: 8 | example: 姓名 9 | type: string 10 | passwd: 11 | example: "123456" 12 | type: string 13 | required: 14 | - age 15 | - name 16 | - passwd 17 | type: object 18 | middleware.Response: 19 | properties: 20 | data: {} 21 | errmsg: 22 | type: string 23 | errno: 24 | type: integer 25 | stack: {} 26 | trace_id: {} 27 | type: object 28 | info: 29 | contact: {} 30 | paths: 31 | /demo/bind: 32 | post: 33 | consumes: 34 | - application/json 35 | description: 测试数据绑定 36 | operationId: /demo/bind 37 | parameters: 38 | - description: body 39 | in: body 40 | name: polygon 41 | required: true 42 | schema: 43 | $ref: '#/definitions/dto.DemoInput' 44 | produces: 45 | - application/json 46 | responses: 47 | "200": 48 | description: success 49 | schema: 50 | allOf: 51 | - $ref: '#/definitions/middleware.Response' 52 | - properties: 53 | data: 54 | $ref: '#/definitions/dto.DemoInput' 55 | type: object 56 | summary: 测试数据绑定 57 | tags: 58 | - 用户 59 | swagger: "2.0" 60 | -------------------------------------------------------------------------------- /kubernetes/agent/agent-deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | labels: 5 | k8s.kuboard.cn/name: gin-mysqlbak-agent 6 | name: gin-mysqlbak-agent 7 | namespace: mysqlbak 8 | spec: 9 | progressDeadlineSeconds: 600 10 | replicas: 1 11 | revisionHistoryLimit: 10 12 | selector: 13 | matchLabels: 14 | app: gin-mysqlbak-agent 15 | strategy: 16 | rollingUpdate: 17 | maxSurge: 25% 18 | maxUnavailable: 25% 19 | type: RollingUpdate 20 | template: 21 | metadata: 22 | creationTimestamp: null 23 | labels: 24 | app: gin-mysqlbak-agent 25 | spec: 26 | containers: 27 | - image: 'chenteng/gin-mysqlbak-agent:3.0.0' 28 | imagePullPolicy: IfNotPresent 29 | name: gin-mysqlbak-agent 30 | terminationMessagePath: /dev/termination-log 31 | terminationMessagePolicy: File 32 | volumeMounts: 33 | - mountPath: /app/domain/config/config.ini 34 | name: baseconf 35 | subPath: config.ini 36 | dnsPolicy: ClusterFirstWithHostNet 37 | hostNetwork: true 38 | nodeSelector: 39 | agent: 'true' 40 | restartPolicy: Always 41 | schedulerName: default-scheduler 42 | terminationGracePeriodSeconds: 30 43 | volumes: 44 | - configMap: 45 | defaultMode: 420 46 | name: mysqlbak-agent-conf 47 | name: baseconf 48 | -------------------------------------------------------------------------------- /front/src/components/FooterToolbar/index.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* 4 | 5 | - [FooterToolbar 底部工具栏](#footertoolbar-%E5%BA%95%E9%83%A8%E5%B7%A5%E5%85%B7%E6%A0%8F) 6 | - [何时使用](#%E4%BD%95%E6%97%B6%E4%BD%BF%E7%94%A8) 7 | - [代码演示](#%E4%BB%A3%E7%A0%81%E6%BC%94%E7%A4%BA) 8 | - [API](#api) 9 | 10 | 11 | 12 | # FooterToolbar 底部工具栏 13 | 14 | 固定在底部的工具栏。 15 | 16 | 17 | 18 | ## 何时使用 19 | 20 | 固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。 21 | 22 | 23 | 24 | 引用方式: 25 | 26 | ```javascript 27 | import FooterToolBar from '@/components/FooterToolbar' 28 | 29 | export default { 30 | components: { 31 | FooterToolBar 32 | } 33 | } 34 | ``` 35 | 36 | 37 | 38 | ## 代码演示 39 | 40 | ```html 41 | 42 | 提交 43 | 44 | ``` 45 | 或 46 | ```html 47 | 48 | 提交 49 | 50 | ``` 51 | 52 | 53 | ## API 54 | 55 | 参数 | 说明 | 类型 | 默认值 56 | ----|------|-----|------ 57 | children (slot) | 工具栏内容,向右对齐 | - | - 58 | extra | 额外信息,向左对齐 | String, Object | - 59 | 60 | -------------------------------------------------------------------------------- /front/src/views/cluster/task/mysql/ClusterMysqlTask.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 60 | -------------------------------------------------------------------------------- /dao/ding.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | type DingDatabase struct { 9 | Id int `gorm:"primary_key" description:"自增主键"` 10 | TaskID int `json:"task_id" gorm:"column:task_id" description:"任务id"` 11 | IsDingSend int `json:"is_ding_send" gorm:"column:is_ding_send" description:"是否发送钉钉消息"` 12 | DingAccessToken string `json:"ding_access_token" gorm:"column:ding_access_token" description:"accessToken"` 13 | DingSecret string `json:"ding_secret" gorm:"column:ding_secret" description:"secret"` 14 | } 15 | 16 | func (s *DingDatabase) TableName() string { 17 | return "t_ding" 18 | } 19 | 20 | func (s *DingDatabase) Save(c *gin.Context, tx *gorm.DB) error { 21 | return tx.WithContext(c).Save(s).Error 22 | } 23 | 24 | func (d *DingDatabase) Updates(c *gin.Context, tx *gorm.DB) error { 25 | return tx.WithContext(c).Table(d.TableName()).Where("task_id = ?", d.TaskID).Updates(map[string]interface{}{ 26 | "task_id": d.TaskID, 27 | "is_ding_send": d.IsDingSend, 28 | "ding_access_token": d.DingAccessToken, 29 | "ding_secret": d.DingSecret, 30 | }).Error 31 | } 32 | 33 | func (s *DingDatabase) Find(c *gin.Context, tx *gorm.DB, search *DingDatabase) (*DingDatabase, error) { 34 | out := &DingDatabase{} 35 | err := tx.WithContext(c).Where(search).Find(out).Error 36 | if err != nil { 37 | return nil, err 38 | } 39 | return out, nil 40 | } 41 | -------------------------------------------------------------------------------- /dao/roledao/user_info.go: -------------------------------------------------------------------------------- 1 | package roledao 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gorm.io/gorm" 6 | ) 7 | 8 | // UserInfo 用户信息表 9 | type UserInfo struct { 10 | Id int `gorm:"column:id;type:int(11);AUTO_INCREMENT;primary_key" json:"id"` 11 | Uid int `gorm:"column:uid;type:int(11)" json:"uid"` 12 | Avatar string `gorm:"column:avatar;type:varchar(255);comment:头像" json:"avatar"` 13 | CreateId string `gorm:"column:create_id;type:varchar(20);comment:创建用户" json:"create_id"` 14 | Email string `gorm:"column:email;type:varchar(30);comment:邮箱" json:"email"` 15 | Mobile string `gorm:"column:mobile;type:varchar(11);comment:手机号" json:"mobile"` 16 | Nickname string `gorm:"column:nickname;type:varchar(20);comment:别名" json:"nickname"` 17 | Introduction string `gorm:"column:introduction;type:varchar(255);comment:介绍" json:"introduction"` 18 | } 19 | 20 | func (u *UserInfo) TableName() string { 21 | return "user_info" 22 | } 23 | 24 | func (u *UserInfo) Save(ctx *gin.Context, tx *gorm.DB) error { 25 | return tx.WithContext(ctx).Table(u.TableName()).Save(u).Error 26 | } 27 | 28 | func (u *UserInfo) Updates(ctx *gin.Context, tx *gorm.DB) error { 29 | return tx.WithContext(ctx).Table(u.TableName()).Updates(u).Error 30 | } 31 | 32 | func (u *UserInfo) Find(ctx *gin.Context, tx *gorm.DB, search *UserInfo) (*UserInfo, error) { 33 | out := &UserInfo{} 34 | return out, tx.WithContext(ctx).Where(search).Find(out).Error 35 | } 36 | -------------------------------------------------------------------------------- /front/src/api/user.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | const api = { 4 | groupList: '/admin/user_group_list', 5 | getUserByGroup: '/admin/userinfo_by_group', 6 | UpdateUserInfo: '/admin/user_info_update', 7 | ChangePwd: '/admin/changepwd', 8 | DeleteUser: '/admin/user_delete', 9 | ResetPassword: '/admin/user_reset_pwd' 10 | } 11 | 12 | export default api 13 | 14 | // 获取用户组列表 15 | export function getgroupList () { 16 | return request({ 17 | url: api.groupList, 18 | method: 'get' 19 | }) 20 | } 21 | 22 | // 根据用户组获取用户列表 23 | export function getUserByGroup (parameter) { 24 | return request({ 25 | url: api.getUserByGroup, 26 | method: 'get', 27 | params: parameter 28 | }) 29 | } 30 | 31 | // 更新用户信息 32 | export function UpdateUserInfo (parameter) { 33 | return request({ 34 | url: api.UpdateUserInfo, 35 | method: 'put', 36 | data: parameter 37 | }) 38 | } 39 | 40 | // 更改用户密码 41 | export function ChangePwd (parameter) { 42 | return request({ 43 | url: api.ChangePwd, 44 | method: 'post', 45 | data: parameter 46 | }) 47 | } 48 | 49 | // 删除用户 50 | export function DeleteUser (parameter) { 51 | return request({ 52 | url: api.DeleteUser, 53 | method: 'delete', 54 | params: parameter 55 | }) 56 | } 57 | 58 | // 重置用户密码 59 | export function ResetUserPassword (parameter) { 60 | return request({ 61 | url: api.ResetPassword, 62 | method: 'get', 63 | params: parameter 64 | }) 65 | } 66 | -------------------------------------------------------------------------------- /front/src/views/result/Error.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 40 | -------------------------------------------------------------------------------- /front/src/api/agent-task_overview.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function GetAgentTaskOverViewList (query) { 4 | return request({ 5 | url: '/agent/overview_task_list', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function stopOverviewBak (query) { 12 | return request({ 13 | url: '/agent/overview_task_stop', 14 | method: 'get', 15 | params: query 16 | }) 17 | } 18 | 19 | export function startOverviewBak (query) { 20 | return request({ 21 | url: '/agent/overview_task_start', 22 | method: 'get', 23 | params: query 24 | }) 25 | } 26 | 27 | export function batchStartOverviewBak (data) { 28 | return request({ 29 | url: '/agent/overview_task_batch_start', 30 | method: 'post', 31 | data 32 | }) 33 | } 34 | 35 | export function batchStopOverviewBak (data) { 36 | return request({ 37 | url: '/agent/overview_task_batch_stop', 38 | method: 'post', 39 | data 40 | }) 41 | } 42 | 43 | export function deleteOverviewBak (query) { 44 | return request({ 45 | url: '/agent/overview_task_delete', 46 | method: 'delete', 47 | params: query 48 | }) 49 | } 50 | 51 | export function restoreOverviewBak (query) { 52 | return request({ 53 | url: '/agent/overview_task_restore', 54 | method: 'put', 55 | params: query 56 | }) 57 | } 58 | 59 | export function syncOverviewBak () { 60 | return request({ 61 | url: '/agent/overview_task_sync', 62 | method: 'get' 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /front/src/components/Charts/Liquid.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /public/params.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/go-playground/universal-translator" 6 | "github.com/pkg/errors" 7 | "gopkg.in/go-playground/validator.v9" 8 | "strings" 9 | ) 10 | 11 | func DefaultGetValidParams(c *gin.Context, params interface{}) error { 12 | if err := c.ShouldBind(params); err != nil { 13 | return err 14 | } 15 | //获取验证器 16 | valid, err := GetValidator(c) 17 | if err != nil { 18 | return err 19 | } 20 | //获取翻译器 21 | trans, err := GetTranslation(c) 22 | if err != nil { 23 | return err 24 | } 25 | err = valid.Struct(params) 26 | if err != nil { 27 | errs := err.(validator.ValidationErrors) 28 | sliceErrs := []string{} 29 | for _, e := range errs { 30 | sliceErrs = append(sliceErrs, e.Translate(trans)) 31 | } 32 | return errors.New(strings.Join(sliceErrs, ",")) 33 | } 34 | return nil 35 | } 36 | 37 | func GetValidator(c *gin.Context) (*validator.Validate, error) { 38 | val, ok := c.Get(ValidatorKey) 39 | if !ok { 40 | return nil, errors.New("未设置验证器") 41 | } 42 | validator, ok := val.(*validator.Validate) 43 | if !ok { 44 | return nil, errors.New("获取验证器失败") 45 | } 46 | return validator, nil 47 | } 48 | 49 | func GetTranslation(c *gin.Context) (ut.Translator, error) { 50 | trans, ok := c.Get(TranslatorKey) 51 | if !ok { 52 | return nil, errors.New("未设置翻译器") 53 | } 54 | translator, ok := trans.(ut.Translator) 55 | if !ok { 56 | return nil, errors.New("获取翻译器失败") 57 | } 58 | return translator, nil 59 | } 60 | -------------------------------------------------------------------------------- /front/src/components/GlobalHeader/RightContent.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 63 | -------------------------------------------------------------------------------- /agent/agentdto/bak.go: -------------------------------------------------------------------------------- 1 | package agentdto 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/noovertime7/gin-mysqlbak/public" 6 | ) 7 | 8 | type StartBakInput struct { 9 | ServiceName string `json:"service_name" form:"service_name" validate:"required" comment:"服务名"` 10 | TaskID int64 `json:"task_id" form:"task_id" validate:"required"` 11 | } 12 | type StopBakInput struct { 13 | ServiceName string `json:"service_name" form:"service_name" validate:"required" comment:"服务名"` 14 | TaskID int64 `json:"task_id" form:"task_id" validate:"required"` 15 | } 16 | type StartBakByHostInput struct { 17 | ServiceName string `json:"service_name" form:"service_name" validate:"required" comment:"服务名"` 18 | HostID int64 `json:"host_id" form:"host_id" validate:"required"` 19 | } 20 | type StopBakByHostInput struct { 21 | ServiceName string `json:"service_name" form:"service_name" validate:"required" comment:"服务名"` 22 | HostID int64 `json:"host_id" form:"host_id" validate:"required"` 23 | } 24 | 25 | func (b *StartBakInput) BindValidParams(ctx *gin.Context) error { 26 | return public.DefaultGetValidParams(ctx, b) 27 | } 28 | 29 | func (b *StopBakInput) BindValidParams(ctx *gin.Context) error { 30 | return public.DefaultGetValidParams(ctx, b) 31 | } 32 | 33 | func (b *StartBakByHostInput) BindValidParams(ctx *gin.Context) error { 34 | return public.DefaultGetValidParams(ctx, b) 35 | } 36 | 37 | func (b *StopBakByHostInput) BindValidParams(ctx *gin.Context) error { 38 | return public.DefaultGetValidParams(ctx, b) 39 | } 40 | -------------------------------------------------------------------------------- /front/src/locales/lang/en-US/setting.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'app.setting.pagestyle': 'Page style setting', 3 | 'app.setting.pagestyle.light': 'Light style', 4 | 'app.setting.pagestyle.dark': 'Dark style', 5 | 'app.setting.pagestyle.realdark': 'RealDark style', 6 | 'app.setting.themecolor': 'Theme Color', 7 | 'app.setting.navigationmode': 'Navigation Mode', 8 | 'app.setting.content-width': 'Content Width', 9 | 'app.setting.fixedheader': 'Fixed Header', 10 | 'app.setting.fixedsidebar': 'Fixed Sidebar', 11 | 'app.setting.sidemenu': 'Side Menu Layout', 12 | 'app.setting.topmenu': 'Top Menu Layout', 13 | 'app.setting.content-width.fixed': 'Fixed', 14 | 'app.setting.content-width.fluid': 'Fluid', 15 | 'app.setting.othersettings': 'Other Settings', 16 | 'app.setting.weakmode': 'Weak Mode', 17 | 'app.setting.copy': 'Copy Setting', 18 | 'app.setting.loading': 'Loading theme', 19 | 'app.setting.copyinfo': 'copy success,please replace defaultSettings in src/config/defaultSettings.js', 20 | 'app.setting.production.hint': 'Setting panel shows in development environment only, please manually modify', 21 | 'app.setting.themecolor.daybreak': 'Daybreak Blue', 22 | 'app.setting.themecolor.dust': 'Dust Red', 23 | 'app.setting.themecolor.volcano': 'Volcano', 24 | 'app.setting.themecolor.sunset': 'Sunset Orange', 25 | 'app.setting.themecolor.cyan': 'Cyan', 26 | 'app.setting.themecolor.green': 'Polar Green', 27 | 'app.setting.themecolor.geekblue': 'Geek Blue', 28 | 'app.setting.themecolor.purple': 'Golden Purple' 29 | } 30 | -------------------------------------------------------------------------------- /front/src/components/SelectLang/index.jsx: -------------------------------------------------------------------------------- 1 | import './index.less' 2 | 3 | import { Icon, Menu, Dropdown } from 'ant-design-vue' 4 | import { i18nRender } from '@/locales' 5 | import i18nMixin from '@/store/i18n-mixin' 6 | 7 | const locales = ['zh-CN', 'en-US'] 8 | const languageLabels = { 9 | 'zh-CN': '简体中文', 10 | 'en-US': 'English' 11 | } 12 | // eslint-disable-next-line 13 | const languageIcons = { 14 | 'zh-CN': '🇨🇳', 15 | 'en-US': '🇺🇸' 16 | } 17 | 18 | const SelectLang = { 19 | props: { 20 | prefixCls: { 21 | type: String, 22 | default: 'ant-pro-drop-down' 23 | } 24 | }, 25 | name: 'SelectLang', 26 | mixins: [i18nMixin], 27 | render () { 28 | const { prefixCls } = this 29 | const changeLang = ({ key }) => { 30 | this.setLang(key) 31 | } 32 | const langMenu = ( 33 | 34 | {locales.map(locale => ( 35 | 36 | 37 | {languageIcons[locale]} 38 | {' '} 39 | {languageLabels[locale]} 40 | 41 | ))} 42 | 43 | ) 44 | return ( 45 | 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | } 53 | 54 | export default SelectLang 55 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.welcome': '欢迎', 3 | 'menu.home': '主页', 4 | 'menu.dashboard': '仪表盘', 5 | 'menu.dashboard.analysis': '分析页', 6 | 'menu.dashboard.monitor': '外部链接', 7 | 'menu.dashboard.workplace': '工作台', 8 | 'menu.form': '表单页', 9 | 'menu.form.basic-form': '基础表单', 10 | 'menu.form.step-form': '分步表单', 11 | 'menu.form.step-form.info': '分步表单(填写转账信息)', 12 | 'menu.form.step-form.confirm': '分步表单(确认转账信息)', 13 | 'menu.form.step-form.result': '分步表单(完成)', 14 | 'menu.form.advanced-form': '高级表单', 15 | 'menu.list': '列表页', 16 | 'menu.list.table-list': '查询表格', 17 | 'menu.list.basic-list': '标准列表', 18 | 'menu.list.card-list': '卡片列表', 19 | 'menu.list.search-list': '搜索列表', 20 | 'menu.list.search-list.articles': '搜索列表(文章)', 21 | 'menu.list.search-list.projects': '搜索列表(项目)', 22 | 'menu.list.search-list.applications': '搜索列表(应用)', 23 | 'menu.profile': '详情页', 24 | 'menu.profile.basic': '基础详情页', 25 | 'menu.profile.advanced': '高级详情页', 26 | 'menu.result': '结果页', 27 | 'menu.result.success': '成功页', 28 | 'menu.result.fail': '失败页', 29 | 'menu.exception': '异常页', 30 | 'menu.exception.not-permission': '403', 31 | 'menu.exception.not-find': '404', 32 | 'menu.exception.server-error': '500', 33 | 'menu.exception.trigger': '触发错误', 34 | 'menu.account': '个人页', 35 | 'menu.account.center': '个人中心', 36 | 'menu.account.settings': '个人设置', 37 | 'menu.account.trigger': '触发报错', 38 | 'menu.account.logout': '退出登录', 39 | 'menu.history': '历史记录', 40 | 'menu.esHistory': '快照记录', 41 | 'menu.mysqlHistory': '数据备份' 42 | } 43 | -------------------------------------------------------------------------------- /conf/config.ini: -------------------------------------------------------------------------------- 1 | [base] 2 | debug_mode="debug" 3 | time_location="Asia/Chongqing" 4 | download_url="http://127.0.0.1:8880" 5 | [http] 6 | addr = ":8880" # 监听地址, default ":8700" 7 | read_timeout = 10 # 读取超时时长 8 | write_timeout = 10 # 写入超时时长 9 | max_header_bytes = 20 # 最大的header大小,二进制位长度 10 | [HostLostAlarms] 11 | enable = false 12 | accessToken = "77f579efbefeefc316b55d3caea1ba1963db2f1319aa7520cbfd9626de073fdc" 13 | secret = "SEC72586e3f7ff6db4b2ad24eac905f308a9ddb0b1b9809af31e5623a14abb424b2" 14 | [dingProxyAgent] 15 | enable = false 16 | addr = 172.20.96.56:39999 17 | title = "测试" 18 | content = "测试环境" 19 | [log] 20 | log_level = "trace" #日志打印最低级别 21 | [log_file_writer] 22 | on = true 23 | log_path = "./logs/gin-mysqlbak.inf.log" 24 | rotate_log_path = "./logs/gin-mysqlbak.inf.log.%Y%M%D%H" 25 | wf_log_path = "./logs/gin-mysqlbak.wf.log" 26 | rotate_wf_log_path = "./logs/gin-mysqlbak.wf.log.%Y%M%D%H" 27 | [log_console_writer] #工作台输出 28 | on = false 29 | color = false 30 | [swagger] 31 | title="gin-mysqlbak swagger API" 32 | desc="This is a sample server celler server." 33 | host="127.0.0.1:8880" 34 | base_path="" 35 | [jaeger] ## 是否开启链路追踪 36 | enable = false 37 | addr = 127.0.0.1:6831 38 | [mysql] 39 | host = 127.0.0.1 40 | port = 3306 41 | user = root 42 | password = chenteng 43 | dbname = gin-mysqlbak 44 | [app] ## 应用相关配置 45 | mysqlAvatar: http://qiniu.yunxue521.top/mysql.jpeg 46 | elasticAvatar: http://qiniu.yunxue521.top/elastic.jpg 47 | [cluster] 48 | ## 集群任务同步周期(分钟) 49 | clusterSyncPeriod = "30 02 * * *" -------------------------------------------------------------------------------- /agent/agentservice/es_bak.go: -------------------------------------------------------------------------------- 1 | package agentservice 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/micro/go-micro/v2/client" 6 | "github.com/noovertime7/gin-mysqlbak/agent/agentdto" 7 | "github.com/noovertime7/gin-mysqlbak/agent/pkg" 8 | "github.com/noovertime7/gin-mysqlbak/agent/proto/esbak" 9 | "sync" 10 | ) 11 | 12 | var ( 13 | clusterEsBakService *EsBakService 14 | EsBakServiceOnce sync.Once 15 | ) 16 | 17 | // GetClusterEsBakService 单例模式 18 | func GetClusterEsBakService() *EsBakService { 19 | EsBakServiceOnce.Do(func() { 20 | clusterEsBakService = &EsBakService{} 21 | }) 22 | return clusterEsBakService 23 | } 24 | 25 | type EsBakService struct{} 26 | 27 | func (e *EsBakService) StartEsBak(ctx *gin.Context, params *agentdto.ESBakStartInput) (*esbak.EsBakOneMessage, error) { 28 | EsBakService, addr, err := pkg.GetESBakService(params.ServiceName) 29 | if err != nil { 30 | return nil, err 31 | } 32 | var ops client.CallOption = func(options *client.CallOptions) { 33 | options.Address = []string{addr} 34 | } 35 | return EsBakService.Start(ctx, &esbak.StartEsBakInput{TaskID: params.ID}, ops) 36 | } 37 | 38 | func (e *EsBakService) StopEsBak(ctx *gin.Context, params *agentdto.ESBakStopInput) (*esbak.EsBakOneMessage, error) { 39 | EsBakService, addr, err := pkg.GetESBakService(params.ServiceName) 40 | if err != nil { 41 | return nil, err 42 | } 43 | var ops client.CallOption = func(options *client.CallOptions) { 44 | options.Address = []string{addr} 45 | } 46 | return EsBakService.Stop(ctx, &esbak.StopEsBakInput{TaskID: params.ID}, ops) 47 | } 48 | -------------------------------------------------------------------------------- /front/src/core/bootstrap.js: -------------------------------------------------------------------------------- 1 | import store from '@/store' 2 | import storage from 'store' 3 | import { 4 | ACCESS_TOKEN, 5 | APP_LANGUAGE, 6 | TOGGLE_CONTENT_WIDTH, 7 | TOGGLE_FIXED_HEADER, 8 | TOGGLE_FIXED_SIDEBAR, TOGGLE_HIDE_HEADER, 9 | TOGGLE_LAYOUT, TOGGLE_NAV_THEME, TOGGLE_WEAK, 10 | TOGGLE_COLOR, TOGGLE_MULTI_TAB 11 | } from '@/store/mutation-types' 12 | import { printANSI } from '@/utils/screenLog' 13 | import defaultSettings from '@/config/defaultSettings' 14 | 15 | export default function Initializer () { 16 | printANSI() // 请自行移除该行. please remove this line 17 | 18 | store.commit(TOGGLE_LAYOUT, storage.get(TOGGLE_LAYOUT, defaultSettings.layout)) 19 | store.commit(TOGGLE_FIXED_HEADER, storage.get(TOGGLE_FIXED_HEADER, defaultSettings.fixedHeader)) 20 | store.commit(TOGGLE_FIXED_SIDEBAR, storage.get(TOGGLE_FIXED_SIDEBAR, defaultSettings.fixSiderbar)) 21 | store.commit(TOGGLE_CONTENT_WIDTH, storage.get(TOGGLE_CONTENT_WIDTH, defaultSettings.contentWidth)) 22 | store.commit(TOGGLE_HIDE_HEADER, storage.get(TOGGLE_HIDE_HEADER, defaultSettings.autoHideHeader)) 23 | store.commit(TOGGLE_NAV_THEME, storage.get(TOGGLE_NAV_THEME, defaultSettings.navTheme)) 24 | store.commit(TOGGLE_WEAK, storage.get(TOGGLE_WEAK, defaultSettings.colorWeak)) 25 | store.commit(TOGGLE_COLOR, storage.get(TOGGLE_COLOR, defaultSettings.primaryColor)) 26 | store.commit(TOGGLE_MULTI_TAB, storage.get(TOGGLE_MULTI_TAB, defaultSettings.multiTab)) 27 | store.commit('SET_TOKEN', storage.get(ACCESS_TOKEN)) 28 | 29 | store.dispatch('setLang', storage.get(APP_LANGUAGE, 'en-US')) 30 | // last step 31 | } 32 | -------------------------------------------------------------------------------- /front/src/locales/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import storage from 'store' 4 | import moment from 'moment' 5 | 6 | // default lang 7 | import enUS from './lang/en-US' 8 | 9 | Vue.use(VueI18n) 10 | 11 | export const defaultLang = 'en-US' 12 | 13 | const messages = { 14 | 'en-US': { 15 | ...enUS 16 | } 17 | } 18 | 19 | const i18n = new VueI18n({ 20 | silentTranslationWarn: true, 21 | locale: defaultLang, 22 | fallbackLocale: defaultLang, 23 | messages 24 | }) 25 | 26 | const loadedLanguages = [defaultLang] 27 | 28 | function setI18nLanguage (lang) { 29 | i18n.locale = lang 30 | // request.headers['Accept-Language'] = lang 31 | document.querySelector('html').setAttribute('lang', lang) 32 | return lang 33 | } 34 | 35 | export function loadLanguageAsync (lang = defaultLang) { 36 | return new Promise(resolve => { 37 | // 缓存语言设置 38 | storage.set('lang', lang) 39 | if (i18n.locale !== lang) { 40 | if (!loadedLanguages.includes(lang)) { 41 | return import(/* webpackChunkName: "lang-[request]" */ `./lang/${lang}`).then(msg => { 42 | const locale = msg.default 43 | i18n.setLocaleMessage(lang, locale) 44 | loadedLanguages.push(lang) 45 | moment.updateLocale(locale.momentName, locale.momentLocale) 46 | return setI18nLanguage(lang) 47 | }) 48 | } 49 | return resolve(setI18nLanguage(lang)) 50 | } 51 | return resolve(lang) 52 | }) 53 | } 54 | 55 | export function i18nRender (key) { 56 | return i18n.t(`${key}`) 57 | } 58 | 59 | export default i18n 60 | -------------------------------------------------------------------------------- /public/printlogo.go: -------------------------------------------------------------------------------- 1 | package public 2 | 3 | import "fmt" 4 | 5 | var logo = ` 6 | /$$$$$$ /$$ /$$ /$$ /$$ 7 | /$$__ $$|__/ | $$| $$ | $$ 8 | | $$ \__/ /$$ /$$$$$$$ /$$$$$$/$$$$ /$$ /$$ /$$$$$$$ /$$$$$$ | $$| $$$$$$$ /$$$$$$ | $$ /$$ 9 | | $$ /$$$$| $$| $$__ $$ /$$$$$$| $$_ $$_ $$| $$ | $$ /$$_____/ /$$__ $$| $$| $$__ $$|____ $$| $$ /$$/ 10 | | $$|_ $$| $$| $$ \ $$|______/| $$ \ $$ \ $$| $$ | $$| $$$$$$ | $$ \ $$| $$| $$ \ $$ /$$$$$$$| $$$$$$/ 11 | | $$ \ $$| $$| $$ | $$ | $$ | $$ | $$| $$ | $$ \____ $$| $$ | $$| $$| $$ | $$/$$__ $$| $$_ $$ 12 | | $$$$$$/| $$| $$ | $$ | $$ | $$ | $$| $$$$$$$ /$$$$$$$/| $$$$$$$| $$| $$$$$$$/ $$$$$$$| $$ \ $$ 13 | \______/ |__/|__/ |__/ |__/ |__/ |__/ \____ $$|_______/ \____ $$|__/|_______/ \_______/|__/ \__/ 14 | /$$ | $$ | $$ 15 | | $$$$$$/ | $$ 16 | \______/ |__/ ` 17 | 18 | func PrintLogo() { 19 | fmt.Println(logo) 20 | fmt.Println("项目地址: https://github.com/noovertime7/gin-mysqlbak") 21 | fmt.Println("前端地址: https://github.com/noovertime7/gin-mysqlbak/tree/main/front") 22 | fmt.Println("集群Client地址: https://github.com/noovertime7/gin-mysqlbak-agent") 23 | } 24 | -------------------------------------------------------------------------------- /router/httpserver.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "context" 5 | "github.com/gin-gonic/gin" 6 | "github.com/noovertime7/gin-mysqlbak/conf" 7 | "github.com/noovertime7/gin-mysqlbak/controller" 8 | "github.com/noovertime7/gin-mysqlbak/server" 9 | "log" 10 | "net/http" 11 | "time" 12 | ) 13 | 14 | var ( 15 | HttpSrvHandler *http.Server 16 | ) 17 | 18 | func HttpServerRun() { 19 | server.Start() 20 | gin.SetMode(conf.GetStringConf("base", "debug_mode")) 21 | r := InitRouter() 22 | HttpSrvHandler = &http.Server{ 23 | Addr: conf.GetStringConf("http", "addr"), 24 | Handler: r, 25 | ReadTimeout: time.Duration(conf.GetIntConf("http", "read_timeout")) * time.Second, 26 | WriteTimeout: time.Duration(conf.GetIntConf("http", "write_timeout")) * time.Second, 27 | MaxHeaderBytes: 1 << uint(conf.GetIntConf("http", "max_header_bytes")), 28 | } 29 | go func() { 30 | log.Printf(" [INFO] HttpServerRun:%s\n", conf.GetStringConf("http", "addr")) 31 | if err := HttpSrvHandler.ListenAndServe(); err != nil { 32 | log.Fatalf(" [ERROR] HttpServerRun:%s err:%v\n", conf.GetStringConf("http", "addr"), err) 33 | } 34 | }() 35 | //如果主机丢失开关打开,才会开启主机检测功能 36 | if conf.GetBoolConf("HostLostAlarms", "enable") { 37 | go controller.HostPortCheck() 38 | } 39 | } 40 | 41 | func HttpServerStop() { 42 | server.Stop() 43 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 44 | defer cancel() 45 | if err := HttpSrvHandler.Shutdown(ctx); err != nil { 46 | log.Fatalf(" [ERROR] HttpServerStop err:%v\n", err) 47 | } 48 | log.Printf(" [INFO] HttpServerStop stopped\n") 49 | } 50 | -------------------------------------------------------------------------------- /agent/agentdto/es_history.go: -------------------------------------------------------------------------------- 1 | package agentdto 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/noovertime7/gin-mysqlbak/public" 6 | ) 7 | 8 | type EsHistoryIDInput struct { 9 | ServiceName string `form:"service_name" json:"service_name" comment:"服务名" validate:"required" example:"test.local"` 10 | ID int64 `json:"id" form:"id" validate:"required"` 11 | } 12 | 13 | type EsHistorySvcName struct { 14 | ServiceName string `form:"service_name" json:"service_name" comment:"服务名" validate:"required" example:"test.local"` 15 | } 16 | 17 | type ESHistoryListInput struct { 18 | ServiceName string `form:"service_name" json:"service_name" comment:"服务名" validate:"required" example:"test.local"` 19 | Info string `form:"info" json:"info" comment:"关键词" validate:"" example:""` 20 | PageNo int64 `form:"page_no" json:"page_no" comment:"每页条数" validate:"" example:"1"` 21 | PageSize int64 `form:"page_size" json:"page_size" comment:"页数" validate:"" example:"20"` 22 | Status string `form:"status" json:"status" validate:""` 23 | SortField string `form:"sortField" json:"sortField" comment:"排序字段" ` 24 | SortOrder string `json:"sortOrder" form:"sortOrder" comment:"排序规则"` 25 | } 26 | 27 | func (d *ESHistoryListInput) BindValidParam(ctx *gin.Context) error { 28 | return public.DefaultGetValidParams(ctx, d) 29 | } 30 | 31 | func (d *EsHistoryIDInput) BindValidParam(ctx *gin.Context) error { 32 | return public.DefaultGetValidParams(ctx, d) 33 | } 34 | 35 | func (d *EsHistorySvcName) BindValidParam(ctx *gin.Context) error { 36 | return public.DefaultGetValidParams(ctx, d) 37 | } 38 | -------------------------------------------------------------------------------- /staging/crypt/cmd/decrypt.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/noovertime7/gin-mysqlbak/staging/crypt/pkg" 6 | "github.com/pkg/errors" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | rootCmd.AddCommand(decrypt) 12 | decrypt.Flags().StringP("fileType", "t", "txt", "File suffix before encryption") 13 | } 14 | 15 | var decrypt = &cobra.Command{ 16 | Use: "decrypt", 17 | Aliases: []string{"de"}, 18 | Short: "Decrypting files using key", 19 | Long: "Use 16-bit (AES-128) 24-byte (AES-192) or 32-byte (AES-256) key to decrypt the file, the key must be the same as the key of the encrypted file", 20 | Example: "crypt decrypt -k 0123456789abcdeasbgted3jikydj3ss -t txt text.data => text.txt", 21 | Run: func(cmd *cobra.Command, args []string) { 22 | //设置recover, 23 | defer func() { 24 | if err := recover(); err != nil { //产生了panic异常 25 | fmt.Println("encrypt err ", err) 26 | } 27 | }() 28 | if len(args) != 1 { 29 | HandleErr("You have entered the wrong parameter, Usage: crypt decrypt -k 0123456789abcdeasbgted3jikydj3ss -t pdf text.bak", errors.New("params error")) 30 | } 31 | key, err := cmd.Flags().GetString("key") 32 | if err != nil { 33 | HandleErr("read key err", err) 34 | } 35 | fileType, err := cmd.Flags().GetString("fileType") 36 | if err != nil { 37 | HandleErr("read key err", err) 38 | } 39 | filePath := args[0] 40 | deFile, err := pkg.Decrypt(key, filePath, fileType) 41 | if err != nil { 42 | HandleErr("decrypt err", err) 43 | } 44 | fmt.Printf("decrypt %v success, decrypt file : %v\n", filePath, deFile) 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /staging/crypt/cmd/encrypt.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/noovertime7/gin-mysqlbak/staging/crypt/pkg" 6 | "github.com/pkg/errors" 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | func init() { 11 | rootCmd.AddCommand(encrypt) 12 | encrypt.Flags().StringP("fileType", "t", "data", "File suffix after encryption") 13 | } 14 | 15 | var encrypt = &cobra.Command{ 16 | Use: "encrypt", 17 | Aliases: []string{"en"}, 18 | Short: "Encrypting files using key", 19 | Long: "Use 16-bit (AES-128) 24-byte (AES-192) or 32-byte (AES-256) key to encrypt the file, the key must be the same as the key of the encrypted file", 20 | Example: "crypt encrypt -k 0123456789abcdeasbgted3jikydj3ss -t data text.txt => text.data", 21 | Run: func(cmd *cobra.Command, args []string) { 22 | //设置recover, 23 | defer func() { 24 | if err := recover(); err != nil { //产生了panic异常 25 | fmt.Println("encrypt err ", err) 26 | } 27 | }() 28 | if len(args) != 1 { 29 | HandleErr("You have entered the wrong parameter, Usage: crypt encrypt -k 0123456789abcdeasbgted3jikydj3ss -t data text.txt", errors.New("params error")) 30 | } 31 | key, err := cmd.Flags().GetString("key") 32 | if err != nil { 33 | HandleErr("read key err", err) 34 | } 35 | fileType, err := cmd.Flags().GetString("fileType") 36 | if err != nil { 37 | HandleErr("read key err", err) 38 | } 39 | filePath := args[0] 40 | enFile, err := pkg.Encrypt(key, filePath, fileType) 41 | if err != nil { 42 | HandleErr("encrypt err", err) 43 | } 44 | fmt.Printf("encrypt %v success, encrypt file : %v\n", filePath, enFile) 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /front/src/components/Charts/Radar.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 65 | 66 | 69 | -------------------------------------------------------------------------------- /kubernetes/conf/mysqlbak-ini.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | data: 4 | config.ini: > 5 | [base] 6 | debug_mode="debug" 7 | time_location="Asia/Chongqing" 8 | download_url="http://10.20.110.51:19009" ## 用于下载备份文件使用,需要与server端svc开放的地址端口相同 9 | [http] 10 | addr = ":8880" # 监听地址, default ":8700" 11 | read_timeout = 10 # 读取超时时长 12 | write_timeout = 10 # 写入超时时长 13 | max_header_bytes = 20 # 最大的header大小,二进制位长度 14 | [HostLostAlarms] 15 | enable = true ## 是否启动主机检测 16 | accessToken = "77f579efbefeefc316b55d3caea1ba1963db2f1319aa7520cbfd9626de073fdc" ## 主机检测钉钉报警配置 17 | secret = "SEC72586e3f7ff6db4b2ad24eac905f308a9ddb0b1b9809af31e5623a14abb424b2" 18 | [dingProxyAgent] 19 | enable = false ## 用于无外网环境下发送钉钉消息 20 | addr = 127.0.0.1:39999 21 | title = "公司测试环境备份平台" 22 | content = "公司测试环境备份平台" 23 | [swagger] 24 | title="gin-mysqlbak swagger API" 25 | desc="This is a sample server celler server." 26 | host="127.0.0.1:8880" 27 | base_path="" 28 | [jaeger] 29 | enable = false 30 | addr = 127.0.0.1:6831 31 | [mysql] 32 | host = 127.0.0.1 33 | port = 3306 34 | user = root 35 | password = chenteng 36 | dbname = gin-mysqlbak 37 | [app] ## 应用相关配置 38 | mysqlAvatar: http://qiniu.yunxue521.top/mysql.jpeg 39 | elasticAvatar: http://qiniu.yunxue521.top/elastic.jpg 40 | [cluster] 41 | ## 集群任务同步周期(分钟) 42 | clusterSyncPeriod = "*/1 * * * *" 43 | kind: ConfigMap 44 | metadata: 45 | annotations: {} 46 | name: mysqlbak-ini-conf 47 | namespace: mysqlbak 48 | 49 | -------------------------------------------------------------------------------- /front/src/views/form/stepForm/StepForm.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 60 | 61 | 67 | -------------------------------------------------------------------------------- /front/src/utils/screenLog.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export const printANSI = () => { 3 | // console.clear() 4 | console.log('[antd pro] created()') 5 | // ASCII - ANSI Shadow 6 | let text = ` 7 | █████╗ ███╗ ██╗████████╗██████╗ ██████╗ ██████╗ ██████╗ 8 | ██╔══██╗████╗ ██║╚══██╔══╝██╔══██╗ ██╔══██╗██╔══██╗██╔═══██╗ 9 | ███████║██╔██╗ ██║ ██║ ██║ ██║ ██████╔╝██████╔╝██║ ██║ 10 | ██╔══██║██║╚██╗██║ ██║ ██║ ██║ ██╔═══╝ ██╔══██╗██║ ██║ 11 | ██║ ██║██║ ╚████║ ██║ ██████╔╝ ██║ ██║ ██║╚██████╔╝ 12 | ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ 13 | \t\t\t\t\tPublished ${APP_VERSION}-${GIT_HASH} @ antdv.com 14 | \t\t\t\t\tBuild date: ${BUILD_DATE}` 15 | console.log(`%c${text}`, 'color: #fc4d50') 16 | console.log('%c感谢使用 antd pro!', 'color: #000; font-size: 14px; font-family: Hiragino Sans GB,Microsoft YaHei,\\\\5FAE\\8F6F\\96C5\\9ED1,Droid Sans Fallback,Source Sans,Wenquanyi Micro Hei,WenQuanYi Micro Hei Mono,WenQuanYi Zen Hei,Apple LiGothic Medium,SimHei,ST Heiti,WenQuanYi Zen Hei Sharp,sans-serif;') 17 | console.log('%cThanks for using antd pro!', 'color: #fff; font-size: 14px; font-weight: 300; text-shadow:#000 1px 0 0,#000 0 1px 0,#000 -1px 0 0,#000 0 -1px 0;') 18 | console.log('') 19 | console.log('%c默认使用的路由初始化模式可能是 静态路由 / 动态路由, 请前往 src/store/index.js 确认 import permission from 哪一个文件.', 'color: #000; font-size: 14px; font-family: Hiragino Sans GB,Microsoft YaHei,\\\\5FAE\\8F6F\\96C5\\9ED1,Droid Sans Fallback,Source Sans,Wenquanyi Micro Hei,WenQuanYi Micro Hei Mono,WenQuanYi Zen Hei,Apple LiGothic Medium,SimHei,ST Heiti,WenQuanYi Zen Hei Sharp,sans-serif;') 20 | console.log('') 21 | } 22 | -------------------------------------------------------------------------------- /kubernetes/gin-mysqlbak-web-deploy.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: gin-mysqlbak-web 6 | namespace: mysqlbak 7 | spec: 8 | progressDeadlineSeconds: 600 9 | replicas: 1 10 | revisionHistoryLimit: 10 11 | selector: 12 | matchLabels: 13 | app: gin-mysqlbak-web 14 | strategy: 15 | rollingUpdate: 16 | maxSurge: 25% 17 | maxUnavailable: 25% 18 | type: RollingUpdate 19 | template: 20 | metadata: 21 | creationTimestamp: null 22 | labels: 23 | app: gin-mysqlbak-web 24 | spec: 25 | containers: 26 | - image: 'chenteng/gin-mysqlbak-web:3.0.0' 27 | imagePullPolicy: IfNotPresent 28 | name: web-pod 29 | volumeMounts: 30 | - mountPath: /etc/nginx/conf.d/default.conf 31 | name: nginx-conf 32 | subPath: default.conf 33 | dnsPolicy: ClusterFirst 34 | restartPolicy: Always 35 | schedulerName: default-scheduler 36 | terminationGracePeriodSeconds: 30 37 | volumes: 38 | - configMap: 39 | defaultMode: 420 40 | name: mysqlbak-web-conf 41 | name: nginx-conf 42 | 43 | --- 44 | apiVersion: v1 45 | kind: Service 46 | metadata: 47 | labels: 48 | app: gin-mysqlbak-web 49 | name: gin-mysqlbak-web 50 | namespace: mysqlbak 51 | spec: 52 | externalTrafficPolicy: Cluster 53 | ipFamilies: 54 | - IPv4 55 | ipFamilyPolicy: SingleStack 56 | ports: 57 | - nodePort: 19008 58 | port: 80 59 | protocol: TCP 60 | targetPort: 80 61 | selector: 62 | app: gin-mysqlbak-web 63 | sessionAffinity: None 64 | type: NodePort 65 | 66 | -------------------------------------------------------------------------------- /front/src/api/bak.js: -------------------------------------------------------------------------------- 1 | import request from '@/utils/request' 2 | 3 | export function startBak (query) { 4 | return request({ 5 | url: '/bak/start', 6 | method: 'get', 7 | params: query 8 | }) 9 | } 10 | 11 | export function stopBak (query) { 12 | return request({ 13 | url: '/bak/stop', 14 | method: 'get', 15 | params: query 16 | }) 17 | } 18 | 19 | export function startAllBakByHost (query) { 20 | return request({ 21 | url: '/bak/start_bak_all_byhost', 22 | method: 'get', 23 | params: query 24 | }) 25 | } 26 | 27 | export function stopAllBakByHost (hostid) { 28 | return request({ 29 | url: '/bak/stop_bak_all_byhost', 30 | method: 'get', 31 | params: hostid 32 | }) 33 | } 34 | 35 | export function startAllBak () { 36 | return request({ 37 | url: '/bak/start_bak_all', 38 | method: 'get' 39 | }) 40 | } 41 | 42 | export function stopAllBak () { 43 | return request({ 44 | url: '/bak/stop_bak_all', 45 | method: 'get' 46 | }) 47 | } 48 | 49 | export function getLocalHistoryList (data) { 50 | console.log(data) 51 | return request({ 52 | url: '/bak/historylist', 53 | method: 'get', 54 | params: data 55 | }) 56 | } 57 | 58 | export function getBakHistoryList () { 59 | return request({ 60 | url: '/bak/findallhistory', 61 | method: 'get' 62 | }) 63 | } 64 | 65 | export function deleteLocalHistory (data) { 66 | console.log(data) 67 | return request({ 68 | url: '/bak/history_delete', 69 | method: 'delete', 70 | params: data 71 | }) 72 | } 73 | 74 | export function getHistoryNumInfo () { 75 | return request({ 76 | url: '/bak/history_num_info', 77 | method: 'get' 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /front/src/components/NumberInfo/NumberInfo.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 68 | 69 | 72 | -------------------------------------------------------------------------------- /front/src/core/permission/permission.js: -------------------------------------------------------------------------------- 1 | export const PERMISSION_ENUM = { 2 | 'add': { key: 'add', label: '新增' }, 3 | 'delete': { key: 'delete', label: '删除' }, 4 | 'edit': { key: 'edit', label: '修改' }, 5 | 'query': { key: 'query', label: '查询' }, 6 | 'get': { key: 'get', label: '详情' }, 7 | 'enable': { key: 'enable', label: '启用' }, 8 | 'disable': { key: 'disable', label: '禁用' }, 9 | 'import': { key: 'import', label: '导入' }, 10 | 'export': { key: 'export', label: '导出' } 11 | } 12 | 13 | /** 14 | * Button 15 | * @param Vue 16 | */ 17 | function plugin (Vue) { 18 | if (plugin.installed) { 19 | return 20 | } 21 | 22 | !Vue.prototype.$auth && Object.defineProperties(Vue.prototype, { 23 | $auth: { 24 | get () { 25 | const _this = this 26 | return (permissions) => { 27 | const [permission, action] = permissions.split('.') 28 | const permissionList = _this.$store.getters.roles.permissions 29 | return permissionList.find((val) => { 30 | return val.permissionId === permission 31 | }).actionList.findIndex((val) => { 32 | return val === action 33 | }) > -1 34 | } 35 | } 36 | } 37 | }) 38 | 39 | !Vue.prototype.$enum && Object.defineProperties(Vue.prototype, { 40 | $enum: { 41 | get () { 42 | // const _this = this; 43 | return (val) => { 44 | let result = PERMISSION_ENUM 45 | val && val.split('.').forEach(v => { 46 | result = result && result[v] || null 47 | }) 48 | return result 49 | } 50 | } 51 | } 52 | }) 53 | } 54 | 55 | export default plugin 56 | -------------------------------------------------------------------------------- /front/src/locales/lang/zh-CN/dashboard/analysis.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 'dashboard.analysis.test': '工专路 {no} 号店', 3 | 'dashboard.analysis.introduce': '指标说明', 4 | 'dashboard.analysis.total-sales': '总销售额', 5 | 'dashboard.analysis.day-sales': '日均销售额¥', 6 | 'dashboard.analysis.visits': '访问量', 7 | 'dashboard.analysis.visits-trend': '访问量趋势', 8 | 'dashboard.analysis.visits-ranking': '门店访问量排名', 9 | 'dashboard.analysis.day-visits': '日访问量', 10 | 'dashboard.analysis.week': '周同比', 11 | 'dashboard.analysis.day': '日同比', 12 | 'dashboard.analysis.payments': '支付笔数', 13 | 'dashboard.analysis.conversion-rate': '转化率', 14 | 'dashboard.analysis.operational-effect': '运营活动效果', 15 | 'dashboard.analysis.sales-trend': '销售趋势', 16 | 'dashboard.analysis.sales-ranking': '门店销售额排名', 17 | 'dashboard.analysis.all-year': '全年', 18 | 'dashboard.analysis.all-month': '本月', 19 | 'dashboard.analysis.all-week': '本周', 20 | 'dashboard.analysis.all-day': '今日', 21 | 'dashboard.analysis.search-users': '搜索用户数', 22 | 'dashboard.analysis.per-capita-search': '人均搜索次数', 23 | 'dashboard.analysis.online-top-search': '线上热门搜索', 24 | 'dashboard.analysis.the-proportion-of-sales': '销售额类别占比', 25 | 'dashboard.analysis.dropdown-option-one': '操作一', 26 | 'dashboard.analysis.dropdown-option-two': '操作二', 27 | 'dashboard.analysis.channel.all': '全部渠道', 28 | 'dashboard.analysis.channel.online': '线上', 29 | 'dashboard.analysis.channel.stores': '门店', 30 | 'dashboard.analysis.sales': '销售额', 31 | 'dashboard.analysis.traffic': '客流量', 32 | 'dashboard.analysis.table.rank': '排名', 33 | 'dashboard.analysis.table.search-keyword': '搜索关键词', 34 | 'dashboard.analysis.table.users': '用户数', 35 | 'dashboard.analysis.table.weekly-range': '周涨幅' 36 | } 37 | -------------------------------------------------------------------------------- /front/src/mock/services/auth.js: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs2' 2 | import { builder, getBody } from '../util' 3 | 4 | const username = ['admin', 'super'] 5 | // 强硬要求 ant.design 相同密码 6 | // '21232f297a57a5a743894a0e4a801fc3', 7 | const password = ['8914de686ab28dc22f30d3d8e107ff6c', '21232f297a57a5a743894a0e4a801fc3'] // admin, ant.design 8 | 9 | const login = (options) => { 10 | const body = getBody(options) 11 | console.log('mock: body', body) 12 | if (!username.includes(body.username) || !password.includes(body.password)) { 13 | return builder({ isLogin: true }, '账户或密码错误', 401) 14 | } 15 | 16 | return builder({ 17 | 'id': Mock.mock('@guid'), 18 | 'name': Mock.mock('@name'), 19 | 'username': 'admin', 20 | 'password': '', 21 | 'avatar': 'https://gw.alipayobjects.com/zos/rmsportal/jZUIxmJycoymBprLOUbT.png', 22 | 'status': 1, 23 | 'telephone': '', 24 | 'lastLoginIp': '27.154.74.117', 25 | 'lastLoginTime': 1534837621348, 26 | 'creatorId': 'admin', 27 | 'createTime': 1497160610259, 28 | 'deleted': 0, 29 | 'roleId': 'admin', 30 | 'lang': 'zh-CN', 31 | 'token': '4291d7da9005377ec9aec4a71ea837f' 32 | }, '', 200, { 'Custom-Header': Mock.mock('@guid') }) 33 | } 34 | 35 | const logout = () => { 36 | return builder({}, '[测试接口] 注销成功') 37 | } 38 | 39 | const smsCaptcha = () => { 40 | return builder({ captcha: Mock.mock('@integer(10000, 99999)') }) 41 | } 42 | 43 | const twofactor = () => { 44 | return builder({ stepCode: Mock.mock('@integer(0, 1)') }) 45 | } 46 | 47 | Mock.mock(/\/auth\/login/, 'post', login) 48 | Mock.mock(/\/auth\/logout/, 'post', logout) 49 | Mock.mock(/\/account\/sms/, 'post', smsCaptcha) 50 | Mock.mock(/\/auth\/2step-code/, 'post', twofactor) 51 | -------------------------------------------------------------------------------- /front/src/components/Charts/ClusterTaskNumChart.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 63 | 64 | 67 | -------------------------------------------------------------------------------- /front/src/components/Ellipsis/Ellipsis.vue: -------------------------------------------------------------------------------- 1 | 65 | --------------------------------------------------------------------------------