├── api ├── files │ └── AppInit │ │ └── BackendJar │ │ ├── origin │ │ └── bin │ │ │ ├── var │ │ │ ├── monitor.sh │ │ │ ├── run.sh │ │ │ └── control │ │ └── jarFuncs ├── conf │ ├── app.ini │ ├── debug.ini │ └── release.ini ├── .gitignore ├── pkg │ ├── util │ │ ├── md5.go │ │ ├── crypt.go │ │ ├── uuid.go │ │ ├── decorators.go │ │ ├── sync.go │ │ ├── pagination.go │ │ ├── hash.go │ │ ├── json.go │ │ ├── arr.go │ │ ├── certs.go │ │ └── string.go │ ├── auth │ │ ├── jwtauth │ │ │ ├── store.go │ │ │ ├── token.go │ │ │ └── jwt_auth.go │ │ └── auth.go │ ├── e │ │ ├── code.go │ │ └── msg.go │ ├── logging │ │ ├── file.go │ │ └── log.go │ ├── file │ │ └── file.go │ ├── upload │ │ └── image.go │ ├── setting │ │ └── setting.go │ └── help │ │ └── helper.go ├── models │ ├── domain.go │ ├── deploy.go │ ├── system.go │ ├── host.go │ ├── notify.go │ ├── cron.go │ ├── conf.go │ ├── metric.go │ ├── email.go │ ├── redis.go │ ├── models.go │ └── admin.go ├── api.iml ├── templates │ └── web_ssh.html ├── middleware │ └── metric.go ├── main.go ├── go.mod ├── controller │ └── admin │ │ └── notify.go └── manage.go ├── web ├── src │ ├── utils │ │ ├── About.js │ │ ├── history.js │ │ ├── getPageTitle.js │ │ ├── getTimeRangeConfig.js │ │ └── globalTools.js │ ├── layouts │ │ ├── index.css │ │ ├── logo.svg │ │ ├── __tests__ │ │ │ └── index.test.js │ │ ├── UserLayout.less │ │ ├── UserLayout.js │ │ └── index.js │ ├── global.css │ ├── app.js │ ├── components │ │ ├── BizCharts │ │ │ ├── TempleTable.less │ │ │ ├── ColumnImageTopChart.less │ │ │ ├── IncomeLineChart.less │ │ │ ├── DailyCard.less │ │ │ ├── DailyCard.js │ │ │ ├── ColumnImageTopChart.js │ │ │ ├── IncomePackageChart.js │ │ │ └── IncomeLineChart.js │ │ ├── Report │ │ │ ├── index.js │ │ │ └── index.module.css │ │ ├── GlobalFooter │ │ │ ├── index.less │ │ │ └── index.js │ │ ├── index.module.css │ │ ├── SearchForm.js │ │ ├── GlobalHeader │ │ │ ├── index.less │ │ │ └── index.js │ │ └── SiderMenu │ │ │ └── index.js │ ├── pages │ │ ├── system │ │ │ ├── login.css │ │ │ ├── role.css │ │ │ ├── index.module.css │ │ │ ├── BasicSetting.js │ │ │ ├── About.js │ │ │ ├── login.js │ │ │ ├── setting.js │ │ │ ├── KeySetting.js │ │ │ ├── LDAPSetting.js │ │ │ └── EmailSetting.js │ │ ├── index.js │ │ ├── schedule │ │ │ ├── index.module.css │ │ │ ├── Record.js │ │ │ └── Info.js │ │ ├── welcome │ │ │ ├── index.js │ │ │ ├── index.module.css │ │ │ ├── info.js │ │ │ ├── Basic.js │ │ │ └── Reset.js │ │ ├── index.css │ │ ├── __tests__ │ │ │ └── index.test.js │ │ ├── deploy │ │ │ ├── do │ │ │ │ ├── OutView.js │ │ │ │ └── index.module.css │ │ │ ├── index.module.css │ │ │ ├── Ex2Info.js │ │ │ ├── Approve.js │ │ │ ├── Template.js │ │ │ ├── SelectTemplate.js │ │ │ └── SelectApp.js │ │ ├── config │ │ │ ├── AddSelect.js │ │ │ ├── Ext2Form.js │ │ │ ├── Ext1Form.js │ │ │ ├── index.module.css │ │ │ ├── AppSync.js │ │ │ ├── Ext2Setup1.js │ │ │ ├── Ext2Setup2.js │ │ │ └── Ext1Setup1.js │ │ └── host │ │ │ └── Import.js │ ├── services │ │ ├── domain.js │ │ ├── menu.js │ │ ├── schedule.js │ │ ├── deploy.js │ │ ├── host.js │ │ ├── config.js │ │ └── user.js │ └── models │ │ ├── domain.js │ │ ├── app.js │ │ └── schedule.js ├── public │ ├── resource │ │ └── 主机导入模板.xlsx │ └── xterm │ │ ├── xterm.min.css │ │ └── xterm-addon-fit.min.js ├── webpack.config.js ├── .editorconfig ├── .gitignore ├── .umirc.js ├── package.json └── mock │ ├── statistic.js │ └── menu.js ├── media ├── 15891946861204.jpg ├── 15891947127483.jpg ├── 15891947391454.jpg ├── 15891947570414.jpg ├── 15928234679632.jpg ├── 15928234981049.jpg ├── 15928236025004.jpg ├── 15953325668987.jpg ├── 15953325809487.jpg ├── 15953326559076.jpg ├── 15953326919764.jpg ├── 15953329352171.jpg ├── 15953329951207.jpg ├── 15958504468516.jpg └── 15958505609770.jpg ├── .gitignore └── LICENSE /api/files/AppInit/BackendJar/origin/bin/var: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/utils/About.js: -------------------------------------------------------------------------------- 1 | export const VERSION = 'v2.1.0'; -------------------------------------------------------------------------------- /api/conf/app.ini: -------------------------------------------------------------------------------- 1 | #debug or release 2 | RUN_MODE = debug 3 | -------------------------------------------------------------------------------- /web/src/layouts/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | font-family: Georgia, sans-serif; 4 | } 5 | -------------------------------------------------------------------------------- /media/15891946861204.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15891946861204.jpg -------------------------------------------------------------------------------- /media/15891947127483.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15891947127483.jpg -------------------------------------------------------------------------------- /media/15891947391454.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15891947391454.jpg -------------------------------------------------------------------------------- /media/15891947570414.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15891947570414.jpg -------------------------------------------------------------------------------- /media/15928234679632.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15928234679632.jpg -------------------------------------------------------------------------------- /media/15928234981049.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15928234981049.jpg -------------------------------------------------------------------------------- /media/15928236025004.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15928236025004.jpg -------------------------------------------------------------------------------- /media/15953325668987.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15953325668987.jpg -------------------------------------------------------------------------------- /media/15953325809487.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15953325809487.jpg -------------------------------------------------------------------------------- /media/15953326559076.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15953326559076.jpg -------------------------------------------------------------------------------- /media/15953326919764.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15953326919764.jpg -------------------------------------------------------------------------------- /media/15953329352171.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15953329352171.jpg -------------------------------------------------------------------------------- /media/15953329951207.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15953329951207.jpg -------------------------------------------------------------------------------- /media/15958504468516.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15958504468516.jpg -------------------------------------------------------------------------------- /media/15958505609770.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/media/15958505609770.jpg -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | /.git/ 2 | .DS_Store 3 | /runtime/* 4 | /.idea/ 5 | /vendor/ 6 | /conf/debug.ini 7 | -------------------------------------------------------------------------------- /web/public/resource/主机导入模板.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junun/admin-go/HEAD/web/public/resource/主机导入模板.xlsx -------------------------------------------------------------------------------- /web/src/global.css: -------------------------------------------------------------------------------- 1 | 2 | html, body, #root { 3 | height: 100%; 4 | } 5 | 6 | body { 7 | margin: 0; 8 | } 9 | -------------------------------------------------------------------------------- /web/src/utils/history.js: -------------------------------------------------------------------------------- 1 | import {createBrowserHistory} from 'history'; 2 | 3 | export default createBrowserHistory() -------------------------------------------------------------------------------- /web/src/app.js: -------------------------------------------------------------------------------- 1 | 2 | export const dva = { 3 | config: { 4 | onError(err) { 5 | err.preventDefault(); 6 | console.error(err.message); 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /web/src/components/BizCharts/TempleTable.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | .upNumber { 3 | color: @success-color; 4 | } 5 | .downNumber { 6 | color: @error-color; 7 | } 8 | -------------------------------------------------------------------------------- /web/src/pages/system/login.css: -------------------------------------------------------------------------------- 1 | .login_form { 2 | max-width: 300px; 3 | margin: 0 auto; 4 | } 5 | .login_form_forgot { 6 | float: right; 7 | } 8 | .login_form_button { 9 | width: 100%; 10 | } -------------------------------------------------------------------------------- /web/src/components/BizCharts/ColumnImageTopChart.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .chartSumLabel { 4 | font-size: 26px; 5 | color: @text-color; 6 | } 7 | 8 | .mainTitle { 9 | font-size: 16px; 10 | } 11 | -------------------------------------------------------------------------------- /web/src/pages/index.js: -------------------------------------------------------------------------------- 1 | import styles from './index.css'; 2 | import {Spin} from "antd"; 3 | 4 | 5 | export default function() { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /api/pkg/util/md5.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | ) 7 | 8 | func EncodeMD5(value string) string { 9 | m := md5.New() 10 | m.Write([]byte(value)) 11 | 12 | return hex.EncodeToString(m.Sum(nil)) 13 | } 14 | -------------------------------------------------------------------------------- /web/src/components/BizCharts/IncomeLineChart.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | .chartSumLabel { 3 | font-size: 26px; 4 | color: @text-color; 5 | } 6 | .chartSumTitle { 7 | font-size: 12px; 8 | color: @text-color-secondary; 9 | } 10 | -------------------------------------------------------------------------------- /web/src/pages/schedule/index.module.css: -------------------------------------------------------------------------------- 1 | .steps { 2 | width: 520px; 3 | margin: 0 auto 30px; 4 | } 5 | 6 | .delIcon { 7 | font-size: 24px; 8 | position: relative; 9 | top: 4px 10 | } 11 | 12 | .delIcon:hover { 13 | color: #f5222d; 14 | } -------------------------------------------------------------------------------- /api/models/domain.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type DomainInfo struct { 6 | Model 7 | Name string 8 | Status int 9 | IsCert int 10 | CertName string 11 | DomainEndTime time.Time 12 | CertEndTime time.Time 13 | Desc string 14 | } 15 | -------------------------------------------------------------------------------- /web/webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 不是真实的 webpack 配置,仅为兼容 webstorm 和 intellij idea 代码跳转 3 | * ref: https://github.com/umijs/umi/issues/1109#issuecomment-423380125 4 | */ 5 | 6 | module.exports = { 7 | resolve: { 8 | alias: { 9 | '@': require('path').resolve(__dirname, 'src'), 10 | }, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /api/pkg/auth/jwtauth/store.go: -------------------------------------------------------------------------------- 1 | package jwtauth 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // Storer 黑名单存储接口 8 | type Storer interface { 9 | // 放入令牌,指定到期时间 10 | Set(tokenString string, expiration time.Duration) error 11 | // 检查令牌是否存在 12 | Check(tokenString string) (bool, error) 13 | // 关闭存储 14 | Close() error 15 | } 16 | -------------------------------------------------------------------------------- /web/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | /package-lock.json 9 | 10 | # production 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | 16 | # umi 17 | .umi 18 | .umi-production 19 | 20 | \.idea/ 21 | -------------------------------------------------------------------------------- /web/src/pages/welcome/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Card } from 'antd'; 3 | 4 | class WelcomePage extends Component { 5 | componentDidMount() { 6 | } 7 | 8 | render() { 9 | return ( 10 | 11 |
欢迎你
12 | 13 |
14 | ) 15 | } 16 | } 17 | 18 | export default WelcomePage; -------------------------------------------------------------------------------- /web/src/components/Report/index.js: -------------------------------------------------------------------------------- 1 | import React, {PureComponent} from "react"; 2 | import { Card } from 'antd'; 3 | 4 | class ReportCard extends PureComponent { 5 | render() { 6 | const { reports } = this.props; 7 | return 8 |
{reports}
9 |
; 10 | } 11 | } 12 | 13 | export default ReportCard; -------------------------------------------------------------------------------- /web/src/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | font-family: Georgia, sans-serif; 4 | margin-top: 4em; 5 | text-align: center; 6 | } 7 | 8 | .welcome { 9 | height: 328px; 10 | } 11 | 12 | .list { 13 | font-size: 1.2em; 14 | margin-top: 1.8em; 15 | list-style: none; 16 | line-height: 1.5em; 17 | } 18 | 19 | .list code { 20 | background: #f7f7f7; 21 | } 22 | -------------------------------------------------------------------------------- /api/api.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | */.DS_Store 15 | */.Archive 16 | */.git 17 | */.idea 18 | .DS_Store 19 | */runtime/* 20 | .idea 21 | /api/conf/release.ini 22 | /api/main -------------------------------------------------------------------------------- /api/pkg/util/crypt.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "golang.org/x/crypto/bcrypt" 4 | 5 | func HashPassword(password string) (string, error) { 6 | bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) 7 | return string(bytes), err 8 | } 9 | 10 | func CheckPasswordHash(password, hash string) bool { 11 | err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) 12 | return err == nil 13 | } 14 | 15 | -------------------------------------------------------------------------------- /web/src/layouts/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | React Logo 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /api/models/deploy.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | type AppDeploy struct { 6 | Model 7 | Tid int 8 | GitType string 9 | Name string 10 | TagBranch string 11 | Commit string 12 | IsPass int 13 | Version string 14 | Reason string 15 | Desc string 16 | Status int 17 | Operator int 18 | Review int 19 | Deploy int 20 | UpdateTime time.Time 21 | } 22 | -------------------------------------------------------------------------------- /api/pkg/util/uuid.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/google/uuid" 5 | ) 6 | 7 | // MustUUID 创建UUID,如果发生错误则抛出panic 8 | func MustUUID() string { 9 | v, err := NewUUID() 10 | if err != nil { 11 | panic(err) 12 | } 13 | return v 14 | } 15 | 16 | // NewUUID 创建UUID 17 | func NewUUID() (string, error) { 18 | v, err := uuid.NewRandom() 19 | if err != nil { 20 | return "", err 21 | } 22 | return v.String(), nil 23 | } 24 | 25 | -------------------------------------------------------------------------------- /api/pkg/e/code.go: -------------------------------------------------------------------------------- 1 | package e 2 | 3 | const ( 4 | SUCCESS = 200 5 | ERROR = 500 6 | INVALID_PARAMS = 400 7 | 8 | ERROR_AUTH_CHECK_TOKEN_FAIL = 20001 9 | ERROR_AUTH_CHECK_TOKEN_TIMEOUT = 20002 10 | ERROR_AUTH_TOKEN = 20003 11 | ERROR_AUTH = 20004 12 | 13 | 14 | // 保存图片失败 15 | ERROR_UPLOAD_SAVE_IMAGE_FAIL = 30001 16 | // 检查图片失败 17 | ERROR_UPLOAD_CHECK_IMAGE_FAIL = 30002 18 | // 校验图片错误,图片格式或大小有问题 19 | ERROR_UPLOAD_CHECK_IMAGE_FORMAT = 30003 20 | ) 21 | -------------------------------------------------------------------------------- /api/models/system.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type About struct { 4 | SystemInfo string 5 | Golangversion string 6 | GinVersion string 7 | } 8 | 9 | type Settings struct { 10 | Model 11 | Name string 12 | Value string 13 | Desc string 14 | } 15 | 16 | type SettingRobot struct { 17 | Model 18 | Name string 19 | Webhook string 20 | Secret string 21 | Keyword string 22 | Type int 23 | Status int 24 | Desc string 25 | } -------------------------------------------------------------------------------- /web/src/pages/system/role.css: -------------------------------------------------------------------------------- 1 | .container :global(.ant-modal-footer) { 2 | border-top: 0 3 | } 4 | 5 | .table { 6 | width: 100%; 7 | border: 1px solid #dfdfdf; 8 | } 9 | 10 | .table :global(.ant-checkbox-group) { 11 | width: 100%; 12 | } 13 | 14 | .table th { 15 | background-color: #fafafa; 16 | color: #404040; 17 | font-size: 18px; 18 | font-weight: 500; 19 | padding: 5px 15px; 20 | } 21 | 22 | .table td { 23 | padding: 5px 15px; 24 | } -------------------------------------------------------------------------------- /web/src/pages/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import Index from '..'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | 5 | describe('Page: index', () => { 6 | it('Render correctly', () => { 7 | const wrapper = renderer.create(); 8 | expect(wrapper.root.children.length).toBe(1); 9 | const outerLayer = wrapper.root.children[0]; 10 | expect(outerLayer.type).toBe('div'); 11 | expect(outerLayer.children.length).toBe(2); 12 | 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /api/files/AppInit/BackendJar/origin/bin/monitor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # 4 | 5 | SHELL_DIR=$(cd $(dirname $0);pwd) 6 | 7 | LOCK_FILE=/dev/shm/`echo ${SHELL_DIR}|sed 's!/!.!g;s!.!!'`.monitor.lock 8 | 9 | { 10 | flock -n 100 || { exit 2; } 11 | 12 | cd ${SHELL_DIR} 13 | function monitor() { 14 | while true;do 15 | ./control monitor 16 | sleep 3 17 | done 18 | } 19 | 20 | monitor >> ../logs/monitor.log 2>&1 & 21 | 22 | } 100<>${LOCK_FILE} -------------------------------------------------------------------------------- /web/src/pages/system/index.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | background-color: #fff; 4 | padding: 16px 0; 5 | } 6 | .left { 7 | flex: 2; 8 | border-right: 1px solid #e8e8e8; 9 | } 10 | .right { 11 | flex: 7; 12 | padding: 8px 40px; 13 | } 14 | 15 | .title { 16 | margin-bottom: 12px; 17 | color: rgba(0, 0, 0, .85); 18 | font-weight: 500; 19 | font-size: 20px; 20 | line-height: 28px; 21 | } 22 | 23 | .form { 24 | max-width: 320px; 25 | } -------------------------------------------------------------------------------- /web/src/pages/welcome/index.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | background-color: #fff; 4 | padding: 16px 0; 5 | } 6 | .left { 7 | flex: 2; 8 | border-right: 1px solid #e8e8e8; 9 | } 10 | .right { 11 | flex: 7; 12 | padding: 8px 40px; 13 | } 14 | 15 | .title { 16 | margin-bottom: 12px; 17 | color: rgba(0, 0, 0, .85); 18 | font-weight: 500; 19 | font-size: 20px; 20 | line-height: 28px; 21 | } 22 | 23 | .form { 24 | max-width: 320px; 25 | } -------------------------------------------------------------------------------- /api/files/AppInit/BackendJar/origin/bin/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source /etc/profile 4 | 5 | WORKSPACE=$(cd $(dirname $0)/; pwd) 6 | cd $WORKSPACE 7 | source var 8 | app=${AppName} 9 | JAVA_OPTS=${JavaOpts} 10 | JAR_BOOTER="${app}.jar" 11 | APP_BASE=/data/webapps/$app 12 | APP_PID=${APP_BASE}/${app}.pid 13 | APP_LOG=$APP_BASE/logs/${app}.log 14 | 15 | cd $(dirname $(find -L $APP_BASE -name $JAR_BOOTER | head -1)) 16 | 17 | java -jar ${JAVA_OPTS} ${JAR_BOOTER} > ${APP_LOG} 2>&1 & 18 | echo $! > ${APP_PID} -------------------------------------------------------------------------------- /web/src/pages/system/BasicSetting.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './index.module.css'; 3 | import { Form} from "antd"; 4 | 5 | class BasicSetting extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = {} 9 | } 10 | 11 | 12 | render() { 13 | return ( 14 | 15 |
基本设置
16 |
17 | ) 18 | } 19 | } 20 | export default Form.create()(BasicSetting) 21 | -------------------------------------------------------------------------------- /api/pkg/util/decorators.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func RequirePermission(perms string) gin.HandlerFunc { 8 | return func(c *gin.Context) { 9 | //if admin.G["is_supper"] != 1 { 10 | // perm_list := strings.Split(perms,"|") 11 | // 12 | // for _, item := range perm_list { 13 | // if !Contains(admin.G["permissions"].([]string), item) { 14 | // JsonRespond(403, "Permission denied", "", c) 15 | // } 16 | // } 17 | //} 18 | 19 | c.Next() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/pkg/util/sync.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "api/pkg/setting" 5 | "os" 6 | "strconv" 7 | ) 8 | 9 | func GetSyncPath() string { 10 | return setting.AppSetting.SyncPath 11 | } 12 | 13 | func ReturnSyncRunDir(appId int) string { 14 | dir, _ := os.Getwd() 15 | path := dir + "/" + GetSyncPath() + "/" + strconv.Itoa(appId) 16 | _, err := os.Stat(path) 17 | if os.IsNotExist(err) { 18 | err := os.MkdirAll(path, os.ModePerm) 19 | if err != nil { 20 | panic(err) 21 | } 22 | } 23 | 24 | return path 25 | } -------------------------------------------------------------------------------- /api/models/host.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // 主机类型 4 | type HostRole struct { 5 | Model 6 | Name string 7 | Desc string 8 | } 9 | 10 | // 主机业务关联 11 | type HostApp struct { 12 | Model 13 | Aid int 14 | Hid int 15 | Status int 16 | Desc string 17 | } 18 | 19 | // 主机信息 20 | type Host struct { 21 | Model 22 | Rid int 23 | EnvId int 24 | ZoneId int 25 | Status int 26 | Enable int 27 | Name string 28 | Addres string 29 | Port int 30 | Username string 31 | Desc string 32 | Operator int 33 | } 34 | -------------------------------------------------------------------------------- /api/pkg/util/pagination.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "api/pkg/setting" 5 | "github.com/gin-gonic/gin" 6 | "github.com/unknwon/com" 7 | ) 8 | 9 | func GetPage(c *gin.Context) int { 10 | result := 0 11 | page, _ := com.StrTo(c.Query("page")).Int() 12 | if page > 0 { 13 | result = (page - 1) * GetPageSize(c) 14 | } 15 | 16 | return result 17 | } 18 | 19 | func GetPageSize(c *gin.Context) int { 20 | result,_ := com.StrTo(c.Query("pagesize")).Int() 21 | 22 | if result == 0 { 23 | return setting.AppSetting.PageSize 24 | } 25 | 26 | return result 27 | } -------------------------------------------------------------------------------- /web/src/layouts/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import BasicLayout from '..'; 2 | import renderer from 'react-test-renderer'; 3 | 4 | describe('Layout: BasicLayout', () => { 5 | it('Render correctly', () => { 6 | const wrapper = renderer.create(); 7 | expect(wrapper.root.children.length).toBe(1); 8 | const outerLayer = wrapper.root.children[0]; 9 | expect(outerLayer.type).toBe('div'); 10 | const title = outerLayer.children[0]; 11 | expect(title.type).toBe('h1'); 12 | expect(title.children[0]).toBe('Yay! Welcome to umi!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /web/src/utils/getPageTitle.js: -------------------------------------------------------------------------------- 1 | const getPageTitle = (pathname, menuData) => { 2 | if (!menuData || menuData.length === 0) { 3 | return ''; 4 | } 5 | let title; 6 | for (let i = 0; i < menuData.length; i++) { 7 | const menu = menuData[i]; 8 | if (menu.children) { 9 | for (let j = 0; j < menu.children.length; j++) { 10 | const subMenu = menu.children[j]; 11 | if (subMenu.Url === pathname) { 12 | title = subMenu.Name; 13 | break; 14 | } 15 | } 16 | } 17 | } 18 | return title; 19 | }; 20 | 21 | export default getPageTitle; 22 | -------------------------------------------------------------------------------- /web/src/components/GlobalFooter/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .globalFooter { 4 | margin: 48px 0 24px 0; 5 | padding: 0 16px; 6 | text-align: center; 7 | 8 | .links { 9 | margin-bottom: 8px; 10 | 11 | a { 12 | color: @text-color-secondary; 13 | transition: all 0.3s; 14 | 15 | &:not(:last-child) { 16 | margin-right: 40px; 17 | } 18 | 19 | &:hover { 20 | color: @text-color; 21 | } 22 | } 23 | } 24 | 25 | .copyright { 26 | color: @text-color-secondary; 27 | font-size: @font-size-base; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/src/services/domain.js: -------------------------------------------------------------------------------- 1 | import { httpGet, httpPost, httpPut, httpDel } from '@/utils/request'; 2 | import { stringify } from 'qs'; 3 | 4 | export async function getDomain(params) { 5 | return httpGet(`/admin/domain/info?${stringify(params)}`); 6 | } 7 | 8 | export async function domainAdd(params) { 9 | return httpPost('/admin/domain/info', params); 10 | } 11 | 12 | export async function domainEdit(params) { 13 | var id = params.id 14 | delete params.id 15 | return httpPut(`/admin/domain/info/${id}`, params); 16 | } 17 | 18 | export async function domainDel(params) { 19 | return httpDel(`/admin/domain/info/${params}`); 20 | } -------------------------------------------------------------------------------- /api/pkg/util/hash.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "crypto/sha1" 6 | "fmt" 7 | ) 8 | 9 | // MD5Hash MD5哈希值 10 | func MD5Hash(b []byte) string { 11 | h := md5.New() 12 | h.Write(b) 13 | return fmt.Sprintf("%x", h.Sum(nil)) 14 | } 15 | 16 | // MD5HashString MD5哈希值 17 | func MD5HashString(s string) string { 18 | return MD5Hash([]byte(s)) 19 | } 20 | 21 | // SHA1Hash SHA1哈希值 22 | func SHA1Hash(b []byte) string { 23 | h := sha1.New() 24 | h.Write(b) 25 | return fmt.Sprintf("%x", h.Sum(nil)) 26 | } 27 | 28 | // SHA1HashString SHA1哈希值 29 | func SHA1HashString(s string) string { 30 | return SHA1Hash([]byte(s)) 31 | } 32 | 33 | -------------------------------------------------------------------------------- /api/templates/web_ssh.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ .title }} 6 | 7 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /api/pkg/auth/jwtauth/token.go: -------------------------------------------------------------------------------- 1 | package jwtauth 2 | 3 | import "encoding/json" 4 | 5 | // tokenInfo 令牌信息 6 | type tokenInfo struct { 7 | AccessToken string `json:"access_token"` // 访问令牌 8 | TokenType string `json:"token_type"` // 令牌类型 9 | ExpiresAt int64 `json:"expires_at"` // 令牌到期时间 10 | } 11 | 12 | func (t *tokenInfo) GetAccessToken() string { 13 | return t.AccessToken 14 | } 15 | 16 | func (t *tokenInfo) GetTokenType() string { 17 | return t.TokenType 18 | } 19 | 20 | func (t *tokenInfo) GetExpiresAt() int64 { 21 | return t.ExpiresAt 22 | } 23 | 24 | func (t *tokenInfo) EncodeToJSON() ([]byte, error) { 25 | return json.Marshal(t) 26 | } 27 | -------------------------------------------------------------------------------- /api/pkg/e/msg.go: -------------------------------------------------------------------------------- 1 | package e 2 | 3 | var MsgFlags = map[int]string { 4 | SUCCESS : "ok", 5 | ERROR : "fail", 6 | INVALID_PARAMS : "请求参数错误", 7 | ERROR_AUTH_CHECK_TOKEN_FAIL : "Token鉴权失败", 8 | ERROR_AUTH_CHECK_TOKEN_TIMEOUT : "Token已超时", 9 | ERROR_AUTH_TOKEN : "Token生成失败", 10 | ERROR_AUTH : "Token错误", 11 | // 保存图片失败 12 | ERROR_UPLOAD_SAVE_IMAGE_FAIL : "保存图片失败", 13 | // 检查图片失败 14 | ERROR_UPLOAD_CHECK_IMAGE_FAIL : "检查图片失败", 15 | // 校验图片错误,图片格式或大小有问题 16 | ERROR_UPLOAD_CHECK_IMAGE_FORMAT : "校验图片错误,图片格式或大小有问题", 17 | } 18 | 19 | func GetMsg(code int) string { 20 | msg, ok := MsgFlags[code] 21 | if ok { 22 | return msg 23 | } 24 | 25 | return MsgFlags[ERROR] 26 | } 27 | -------------------------------------------------------------------------------- /api/pkg/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "errors" 4 | 5 | // 定义错误 6 | var ( 7 | ErrInvalidToken = errors.New("invalid token") 8 | ) 9 | 10 | // TokenInfo 令牌信息 11 | type TokenInfo interface { 12 | // 获取访问令牌 13 | GetAccessToken() string 14 | // 获取令牌类型 15 | GetTokenType() string 16 | // 获取令牌到期时间戳 17 | GetExpiresAt() int64 18 | // JSON编码 19 | EncodeToJSON() ([]byte, error) 20 | } 21 | 22 | // Auther 认证接口 23 | type Auther interface { 24 | // 生成令牌 25 | GenerateToken(userID string) (TokenInfo, error) 26 | 27 | // 销毁令牌 28 | DestroyToken(accessToken string) error 29 | 30 | // 解析用户ID 31 | ParseUserID(accessToken string) (string, error) 32 | 33 | // 释放资源 34 | Release() error 35 | } 36 | -------------------------------------------------------------------------------- /web/src/components/BizCharts/DailyCard.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | .dailyCardTitle { 3 | font-size: 16px; 4 | color: @text-color-secondary; 5 | } 6 | .dailyCardDetail { 7 | font-size: 14px; 8 | color: @text-color-secondary; 9 | } 10 | .dailyCardValue { 11 | font-size: 40px; 12 | color: @text-color; 13 | font-weight: 600; 14 | } 15 | .dailyCardValueSuffix{ 16 | color: @text-color-secondary; 17 | font-size: 12px; 18 | } 19 | .dailyCardRate { 20 | font-size: 12px; 21 | color: @text-color-secondary; 22 | } 23 | .dailyCardRateUp { 24 | font-size: 12px; 25 | color: @success-color; 26 | } 27 | .dailyCardRateDown { 28 | font-size: 12px; 29 | color: @error-color; 30 | } 31 | -------------------------------------------------------------------------------- /web/src/pages/deploy/do/OutView.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './index.module.css'; 3 | import lds from 'lodash'; 4 | 5 | class OutView extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.el = null; 9 | } 10 | 11 | componentDidUpdate(prevProps, prevState, snapshot) { 12 | if (this.el != null) { 13 | this.el.scrollTop = this.el.scrollHeight 14 | } 15 | } 16 | 17 | render() { 18 | const outputs = lds.get(this.props.outputs, `${this.props.id}.Data`, []); 19 | return ( 20 |
 this.el = el} className={styles.ext1Console}>
21 |         {outputs}
22 |       
23 | ) 24 | } 25 | } 26 | 27 | export default OutView -------------------------------------------------------------------------------- /api/models/notify.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "api/pkg/logging" 5 | "time" 6 | ) 7 | 8 | // 通知表 9 | type Notify struct { 10 | Model 11 | Title string 12 | Type int 13 | Source int 14 | Unread int 15 | Content string 16 | Link string 17 | CreateTime time.Time 18 | } 19 | 20 | // 通用生成通知记录方法 21 | func MakeNotify(mytype, source int, title, content, link string) { 22 | var data Notify 23 | data.Type = mytype 24 | data.Source = source 25 | data.Unread = 1 26 | data.Title = title 27 | data.Content = content 28 | data.CreateTime = time.Now().AddDate(0,0,0) 29 | data.Link = link 30 | 31 | e := DB.Save(&data).Error 32 | 33 | if e != nil { 34 | logging.Error("Add Notify Failed. Error :" + e.Error()) 35 | } 36 | } -------------------------------------------------------------------------------- /web/src/components/GlobalFooter/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './index.less'; 3 | 4 | const GlobalFooter = ({ links, copyright }) => { 5 | return ( 6 |
7 | {links && ( 8 |
9 | {links.map(link => ( 10 | 16 | {link.title} 17 | 18 | ))} 19 |
20 | )} 21 | {copyright &&
{copyright}
} 22 |
23 | ); 24 | }; 25 | 26 | export default GlobalFooter; 27 | -------------------------------------------------------------------------------- /web/src/components/index.module.css: -------------------------------------------------------------------------------- 1 | .searchForm :global(.ant-form-item) { 2 | display: flex; 3 | } 4 | 5 | .searchForm :global(.ant-form-item-control-wrapper) { 6 | flex: 1; 7 | } 8 | 9 | .searchForm :global(.ant-form-item-label) { 10 | padding-right: 8px; 11 | } 12 | 13 | .statisticsCard { 14 | position: relative; 15 | text-align: center; 16 | } 17 | 18 | .statisticsCard span { 19 | color: rgba(0, 0, 0, .45); 20 | display: inline-block; 21 | line-height: 22px; 22 | margin-bottom: 4px; 23 | } 24 | 25 | .statisticsCard p { 26 | font-size: 32px; 27 | line-height: 32px; 28 | margin: 0; 29 | } 30 | 31 | .statisticsCard em { 32 | background-color: #e8e8e8; 33 | position: absolute; 34 | height: 56px; 35 | width: 1px; 36 | top: 0; 37 | right: 0; 38 | } 39 | -------------------------------------------------------------------------------- /api/files/AppInit/BackendJar/origin/bin/control: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | 4 | WORKSPACE=$(cd $(dirname $0)/; pwd) 5 | cd $WORKSPACE 6 | source var 7 | 8 | app=${AppName} 9 | source /etc/profile 10 | 11 | APP_BASE=/data/webapps/$app 12 | APP_PID=${APP_BASE}/${app}.pid 13 | CATALINA_TMPDIR=$APP_BASE/temp 14 | 15 | APP_LOG=$APP_BASE/logs/${app}.log 16 | GC_LOG=$APP_BASE/logs/gc.log 17 | 18 | JAVA_OPTS=${JavaOpts} 19 | JAR_BOOTER="${app}.jar" 20 | FORCE_KILL_TIME="30" 21 | #HEALTH_CHECK_METRIC="" 22 | 23 | export JAVA_HOME JAR_BOOTER JAVA_OPTS APP_LOG APP_PID 24 | 25 | cd $(dirname $(find -L $APP_BASE -name $JAR_BOOTER | head -1)) 26 | 27 | . /etc/init.d/jarFuncs 28 | 29 | case "$1" in 30 | start|stop|restart|monitor|log) 31 | $1 32 | ;; 33 | *) 34 | echo $"Usage: $0 {start|stop|restart|monitor|log}" 35 | exit 2 36 | esac -------------------------------------------------------------------------------- /api/middleware/metric.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "api/models" 5 | "github.com/gin-gonic/gin" 6 | "github.com/prometheus/client_golang/prometheus" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | func MetricMiddleware() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | tBegin := time.Now() 14 | 15 | // 执行下一步逻辑 16 | c.Next() 17 | 18 | duration := float64(time.Since(tBegin)) / float64(time.Second) 19 | 20 | // 请求数加1 21 | models.HTTPReqTotal.With(prometheus.Labels{ 22 | "method": c.Request.Method, 23 | "path": c.Request.URL.Path, 24 | "status": strconv.Itoa(c.Writer.Status()), 25 | }).Inc() 26 | 27 | // 记录本次请求处理时间 28 | models.HTTPReqDuration.With(prometheus.Labels{ 29 | "method": c.Request.Method, 30 | "path": c.Request.URL.Path, 31 | }).Observe(duration) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /web/src/pages/deploy/index.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | background-color: #fff; 4 | padding: 16px 0; 5 | min-height: 500px; 6 | } 7 | .left { 8 | flex: 2; 9 | border-right: 1px solid #e8e8e8; 10 | } 11 | .right { 12 | flex: 7; 13 | padding: 8px 40px; 14 | } 15 | 16 | .title { 17 | display: flex; 18 | justify-content: space-between; 19 | align-items: center; 20 | margin-bottom: 12px; 21 | color: rgba(0, 0, 0, .85); 22 | font-weight: 500; 23 | font-size: 20px; 24 | line-height: 28px; 25 | } 26 | 27 | .appBlock { 28 | margin-top: 20px; 29 | width: 165px; 30 | height: 60px; 31 | font-size: 18px; 32 | margin-right: 20px; 33 | } 34 | 35 | .tips { 36 | margin-top: 32px; 37 | color: #888; 38 | } 39 | 40 | .steps { 41 | width: 520px; 42 | margin: 0 auto 30px; 43 | } -------------------------------------------------------------------------------- /web/src/pages/deploy/do/index.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | display: flex; 3 | } 4 | 5 | .collapse { 6 | margin-top: 16px; 7 | } 8 | 9 | .collapse :global(.ant-collapse-content-box) { 10 | padding: 0; 11 | } 12 | 13 | .ext1Console { 14 | min-height: 40px; 15 | max-height: 300px; 16 | padding: 10px 15px; 17 | } 18 | 19 | .ext2Block { 20 | display: flex; 21 | background-color: #fff; 22 | margin-top: 16px; 23 | border-radius: 4px; 24 | border: 1px solid #d9d9d9; 25 | } 26 | 27 | .ext2Console { 28 | flex: 1; 29 | padding: 30px; 30 | } 31 | 32 | .ext2Step { 33 | padding: 24px; 34 | width: 220px; 35 | border-right: 1px solid #e8e8e8; 36 | } 37 | 38 | .ext2Step :global(.ant-steps-item) { 39 | height: 100px; 40 | } 41 | 42 | .ext2Tips { 43 | color: #888; 44 | margin-top: 30px; 45 | } 46 | 47 | pre { 48 | margin: 0; 49 | } -------------------------------------------------------------------------------- /api/models/cron.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/robfig/cron/v3" 5 | "time" 6 | ) 7 | 8 | var ( 9 | CronMain *cron.Cron 10 | ) 11 | 12 | type MyJob struct { 13 | Name string 14 | Func func() 15 | } 16 | 17 | func (j MyJob) Run() { 18 | j.Func() 19 | } 20 | 21 | // 任务表 22 | type Task struct { 23 | Model 24 | Name string 25 | IsMore int 26 | Active int 27 | TriggerType int 28 | HostIds string 29 | Command string 30 | Spec string 31 | Operator int 32 | Desc string 33 | StartTime time.Time 34 | EndTime time.Time 35 | } 36 | 37 | 38 | // 任务执行历史信息表 39 | type TaskHistory struct { 40 | Model 41 | TaskId int 42 | HostId int 43 | Status int 44 | RunTime string 45 | Output string 46 | CreateTime time.Time 47 | } 48 | 49 | 50 | func init() { 51 | CronMain = cron.New() 52 | CronMain.Start() 53 | } 54 | -------------------------------------------------------------------------------- /web/.umirc.js: -------------------------------------------------------------------------------- 1 | // ref: https://umijs.org/config/ 2 | export default { 3 | treeShaking: true, 4 | plugins: [ 5 | // ref: https://umijs.org/plugin/umi-plugin-react.html 6 | ['umi-plugin-react', { 7 | antd: true, 8 | dva: true, 9 | dynamicImport: false, 10 | title: 'demoweb', 11 | dll: false, 12 | 13 | routes: { 14 | exclude: [ 15 | /models\//, 16 | /services\//, 17 | /model\.(t|j)sx?$/, 18 | /service\.(t|j)sx?$/, 19 | /components\//, 20 | ], 21 | }, 22 | }], 23 | ], 24 | hash: true, 25 | "proxy": { 26 | "/admin": { 27 | // "target": "http://10.101.1.152:9090/", 28 | "target": "http://localhost:9090", 29 | request_timeout: 12000, 30 | "changeOrigin": true, 31 | // pathRewrite: { 32 | // '^/api': '' 33 | // } 34 | }, 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /api/conf/debug.ini: -------------------------------------------------------------------------------- 1 | [app] 2 | PageSize = 10 3 | JWT_SECRET = c6b9221d2f0f0e900 4 | RuntimeRootPath = runtime/ 5 | 6 | ImagePrefixUrl = http://127.0.0.1:9090 7 | ImageSavePath = upload/images/ 8 | # MB 9 | ImageMaxSize = 10 10 | ImageAllowExts = .jpg,.jpeg,.png 11 | 12 | LogSavePath = logs/ 13 | LogSaveName = log 14 | LogFileExt = log 15 | TimeFormat = 20060102 16 | IdRsaPath = runtime/ssh/ 17 | GitLocalPath = runtime/git/ 18 | SyncPath = runtime/sync/ 19 | 20 | [server] 21 | HttpPort = 9090 22 | ReadTimeout = 60 23 | WriteTimeout = 60 24 | 25 | [database] 26 | TYPE = mysql 27 | USER = root 28 | PASSWORD = `Redhat#1bm` 29 | HOST = 127.0.0.1:3306 30 | NAME = admin-go-api 31 | 32 | [redis] 33 | ADDRESS = 127.0.0.1:6379 34 | PASSWD = '' 35 | DB = 0 36 | 37 | # dingtake 地址和秘钥 38 | [dingsecret] 39 | webhook = "https://oapi.dingtalk.com/robot/send?access_token=xxx" 40 | secret = "xxx" 41 | 42 | -------------------------------------------------------------------------------- /web/src/components/SearchForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Row, Col, Form } from 'antd'; 3 | import styles from './index.module.css'; 4 | import lodash from "lodash"; 5 | 6 | 7 | export default class extends React.Component { 8 | static Item(props) { 9 | return ( 10 | 11 | {props.children} 12 | 13 | ) 14 | } 15 | 16 | render() { 17 | let items = lodash.get(this.props, 'children', []); 18 | if (!lodash.isArray(items)) items = [items]; 19 | return ( 20 |
21 | 22 | {items.filter(item => item).map((item, index) => ( 23 | 24 | {item} 25 | 26 | ))} 27 | 28 |
29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /web/src/components/GlobalHeader/index.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .pageTitle { 4 | font-size: 18px; 5 | font-weight: 600; 6 | } 7 | 8 | .trigger{ 9 | display: block; 10 | font-size: 20px; 11 | line-height: 64px; 12 | cursor: pointer; 13 | transition: all 0.3s, padding 0s; 14 | padding: 0 24px; 15 | float: right; 16 | } 17 | 18 | .userMenu { 19 | display: block; 20 | float: right; 21 | } 22 | .userMenu span { 23 | margin-left: 5px; 24 | } 25 | 26 | .notify { 27 | width: 350px; 28 | padding: 0; 29 | } 30 | .notify :global(.ant-dropdown-menu-item:hover) { 31 | background-color: #fff; 32 | } 33 | .notifyItem { 34 | align-items: center; 35 | cursor: pointer; 36 | padding: 12px 24px; 37 | } 38 | .notifyItem:hover { 39 | background-color: rgb(233, 247, 254); 40 | } 41 | .notifyFooter { 42 | line-height: 46px; 43 | text-align: center; 44 | cursor: pointer; 45 | border-top: 1px solid #e8e8e8; 46 | } -------------------------------------------------------------------------------- /web/src/components/Report/index.module.css: -------------------------------------------------------------------------------- 1 | 2 | .modal { 3 | position: relative; 4 | /* text-align: center;*/ 5 | } 6 | 7 | 8 | .modal ul { 9 | color: rgba(0, 0, 0, .45); 10 | display: inline-block; 11 | line-height: 22px; 12 | margin-bottom: 4px; 13 | width: 100%; 14 | height:300px; 15 | overflow:auto; 16 | } 17 | 18 | .modaldiv { 19 | display: inline-block; 20 | line-height: 22px; 21 | margin-bottom: 4px; 22 | width: 100%; 23 | height:300px; 24 | overflow:auto; 25 | } 26 | .modaldiv div { 27 | color: rgba(0, 0, 0, .45); 28 | margin-bottom: 12px; 29 | font-weight: 500; 30 | font-size: 16px; 31 | /* display: inline-block;*/ 32 | } 33 | 34 | 35 | .modal p { 36 | font-size: 32px; 37 | line-height: 32px; 38 | margin: 0; 39 | } 40 | 41 | .modal em { 42 | background-color: #e8e8e8; 43 | position: absolute; 44 | height: 56px; 45 | width: 1px; 46 | top: 0; 47 | right: 0; 48 | } 49 | -------------------------------------------------------------------------------- /api/conf/release.ini: -------------------------------------------------------------------------------- 1 | [app] 2 | PageSize = 10 3 | JWT_SECRET = c6b9221d2f0f0e900 4 | RuntimeRootPath = runtime/ 5 | 6 | ImagePrefixUrl = http://127.0.0.1:9090 7 | ImageSavePath = upload/images/ 8 | # MB 9 | ImageMaxSize = 10 10 | ImageAllowExts = .jpg,.jpeg,.png 11 | 12 | LogSavePath = logs/ 13 | LogSaveName = log 14 | LogFileExt = log 15 | TimeFormat = 20060102 16 | # IdRsaPath = runtime/ssh/ 17 | GitLocalPath = runtime/git/ 18 | SyncPath = runtime/sync/ 19 | 20 | 21 | [server] 22 | HttpPort = 9090 23 | ReadTimeout = 60 24 | WriteTimeout = 60 25 | 26 | [database] 27 | TYPE = mysql 28 | USER = omp 29 | PASSWORD = `xxx` 30 | HOST = x.x.x.x:3306 31 | NAME = admin-go-api 32 | 33 | [redis] 34 | ADDRESS = 127.0.0.1:6379 35 | PASSWD = '' 36 | DB = 0 37 | 38 | [business_key] 39 | role_perms = role_perms_set 40 | 41 | # dingtake 地址和秘钥 42 | [dingsecret] 43 | webhook = "https://oapi.dingtalk.com/robot/send?access_token=xxxx" 44 | secret = "xxxx" 45 | -------------------------------------------------------------------------------- /web/src/services/menu.js: -------------------------------------------------------------------------------- 1 | import { httpGet, httpPost, httpPut, httpDel } from '@/utils/request'; 2 | import { stringify } from 'qs'; 3 | 4 | export async function getMenus(params) { 5 | return httpGet(`/admin/menus?${stringify(params)}`); 6 | } 7 | 8 | export async function menuAdd(params) { 9 | return httpPost('/admin/menus', params); 10 | } 11 | 12 | export async function menuEdit(params) { 13 | return httpPut(`/admin/menus/${params.id}`, params); 14 | } 15 | 16 | export async function menuDel(params) { 17 | return httpDel(`/admin/menus/${params}`); 18 | } 19 | 20 | export async function getSubMenus(params) { 21 | return httpGet(`/admin/submenus?${stringify(params)}`); 22 | } 23 | 24 | export async function subMenuAdd(params) { 25 | return httpPost('/admin/submenus', params); 26 | } 27 | 28 | export async function subMenuEdit(params) { 29 | return httpPut(`/admin/submenus/${params.id}`, params); 30 | } 31 | 32 | export async function subMenuDel(params) { 33 | return httpDel(`/admin/submenus/${params}`); 34 | } -------------------------------------------------------------------------------- /api/models/conf.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | // 环境分类表 4 | type ConfigEnv struct { 5 | Model 6 | Name string 7 | Desc string 8 | } 9 | 10 | type AppType struct { 11 | Model 12 | Name string 13 | Desc string 14 | } 15 | 16 | type App struct { 17 | Model 18 | Tid int 19 | EnvId int 20 | Name string 21 | Active int 22 | DeployType int 23 | EnableSync int 24 | Desc string 25 | } 26 | 27 | type DeployExtend struct { 28 | Dtid int 29 | Aid int 30 | Tag string 31 | TemplateName string 32 | EnableCheck int 33 | NotifyId int 34 | HostIds string 35 | RepoUrl string 36 | Versions int 37 | Extend int 38 | FilterRule string 39 | CustomEnvs string 40 | PreCode string 41 | PostCode string 42 | PreDeploy string 43 | PostDeploy string 44 | DstDir string 45 | DstRepo string 46 | } 47 | 48 | type AppSyncValue struct { 49 | Model 50 | Aid int 51 | Name string 52 | Value string 53 | Desc string 54 | } 55 | -------------------------------------------------------------------------------- /api/pkg/util/json.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | jsoniter "github.com/json-iterator/go" 6 | ) 7 | 8 | // 定义JSON操作 9 | var ( 10 | json = jsoniter.ConfigCompatibleWithStandardLibrary 11 | JSONMarshal = json.Marshal 12 | JSONUnmarshal = json.Unmarshal 13 | JSONMarshalIndent = json.MarshalIndent 14 | JSONNewDecoder = json.NewDecoder 15 | JSONNewEncoder = json.NewEncoder 16 | ) 17 | 18 | // JSONMarshalToString JSON编码为字符串 19 | func JSONMarshalToString(v interface{}) string { 20 | s, err := jsoniter.MarshalToString(v) 21 | if err != nil { 22 | return "" 23 | } 24 | return s 25 | } 26 | 27 | func JsonUnmarshalFromString(str string, v interface{}) interface{} { 28 | e := jsoniter.UnmarshalFromString(str, v) 29 | if e != nil { 30 | return "" 31 | } 32 | return v 33 | } 34 | 35 | func JsonRespond(code int, message string, data interface{}, c *gin.Context) { 36 | c.JSON(code, gin.H{ 37 | "code" : code, 38 | "message": message, 39 | "data" : data, 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /web/src/services/schedule.js: -------------------------------------------------------------------------------- 1 | import { httpGet, httpPost, httpPatch, httpPut, httpDel } from '@/utils/request'; 2 | import { stringify } from 'qs'; 3 | 4 | export async function getSchedule(params) { 5 | return httpGet(`/admin/schedule?${stringify(params)}`); 6 | } 7 | 8 | export async function getScheduleHis(params) { 9 | var id = params.id 10 | delete params.id 11 | return httpGet(`/admin/schedule/${id}?${stringify(params)}`); 12 | } 13 | 14 | export async function getScheduleInfo(params) { 15 | var id = params.id 16 | delete params.id 17 | return httpGet(`/admin/schedule/${id}/info?${stringify(params)}`); 18 | } 19 | 20 | export async function changeScheduleActive(params) { 21 | return httpPatch(`/admin/schedule`, params); 22 | } 23 | 24 | export async function scheduleAdd(params) { 25 | return httpPost('/admin/schedule', params); 26 | } 27 | 28 | export async function scheduleEdit(params) { 29 | var id = params.id 30 | delete params.id 31 | return httpPut(`/admin/schedule/${id}`, params); 32 | } 33 | 34 | export async function scheduleDel(params) { 35 | return httpDel(`/admin/schedule/${params}`); 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 openspug 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 | -------------------------------------------------------------------------------- /api/pkg/util/arr.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func StringInSlice(a string, list []string) bool { 9 | for _, b := range list { 10 | if b == a { 11 | return true 12 | } 13 | } 14 | return false 15 | } 16 | 17 | // 空struct不占内存空间,可谓巧妙 18 | func RemoveDuplicateElement(addrs []string) []string { 19 | result := make([]string, 0, len(addrs)) 20 | temp := map[string]struct{}{} 21 | for _, item := range addrs { 22 | if _, ok := temp[item]; !ok { 23 | temp[item] = struct{}{} 24 | result = append(result, item) 25 | } 26 | } 27 | return result 28 | } 29 | 30 | func StrArrContains(slice []string, item string) bool { 31 | set := make(map[string]struct{}, len(slice)) 32 | for _, s := range slice { 33 | set[s] = struct{}{} 34 | } 35 | 36 | _, ok := set[item] 37 | return ok 38 | } 39 | 40 | 41 | func IntArrToString(arr []int, delim string) string { 42 | return strings.Trim(strings.Replace(fmt.Sprint(arr), " ", delim, -1), "[]") 43 | //return strings.Trim(strings.Join(strings.Split(fmt.Sprint(arr), " "), delim), "[]") 44 | //return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(arr)), delim), "[]") 45 | } 46 | -------------------------------------------------------------------------------- /api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "api/controller/admin" 5 | "api/pkg/setting" 6 | "api/routers" 7 | "fmt" 8 | "net/http" 9 | ) 10 | 11 | // @title Swagger Example API 12 | // @version 1.0 13 | // @description This is a sample server celler server. 14 | // @termsOfService https://github.com/junun/admin-go 15 | 16 | // @contact.name Junun 17 | // @contact.url https://github.com/junun/admin-go 18 | // @contact.email junun717@gmail.com 19 | 20 | // @license.name Apache 2.0 21 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html 22 | 23 | // @host 127.0.0.1:9090 24 | // @BasePath / 25 | 26 | 27 | func main() { 28 | // 主进程运行期间启动一个定时任务协程 29 | go func() { 30 | //admin.CheckDomainAndCretCronTask() 31 | admin.StartCronJobsOnBoot() 32 | //fmt.Println(models.CronMain.Entries()) 33 | }() 34 | 35 | r := routers.InitRouter() 36 | 37 | s := &http.Server{ 38 | Addr: fmt.Sprintf(":%d", setting.ServerSetting.HttpPort), 39 | Handler: r, 40 | ReadTimeout: setting.ServerSetting.ReadTimeout, 41 | WriteTimeout: setting.ServerSetting.WriteTimeout, 42 | MaxHeaderBytes: 1 << 20, 43 | } 44 | 45 | s.ListenAndServe() 46 | } 47 | -------------------------------------------------------------------------------- /api/pkg/logging/file.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "time" 7 | "fmt" 8 | ) 9 | 10 | var ( 11 | LogSavePath = "runtime/logs/" 12 | LogSaveName = "log" 13 | LogFileExt = "log" 14 | TimeFormat = "20060102" 15 | ) 16 | 17 | func getLogFilePath() string { 18 | return fmt.Sprintf("%s", LogSavePath) 19 | } 20 | 21 | func getLogFileFullPath() string { 22 | prefixPath := getLogFilePath() 23 | suffixPath := fmt.Sprintf("%s%s.%s", LogSaveName, time.Now().Format(TimeFormat), LogFileExt) 24 | 25 | return fmt.Sprintf("%s%s", prefixPath, suffixPath) 26 | } 27 | 28 | func openLogFile(filePath string) *os.File { 29 | _, err := os.Stat(filePath) 30 | switch { 31 | case os.IsNotExist(err): 32 | mkDir() 33 | case os.IsPermission(err): 34 | log.Fatalf("Permission :%v", err) 35 | } 36 | 37 | handle, err := os.OpenFile(filePath, os.O_APPEND | os.O_CREATE | os.O_WRONLY, 0644) 38 | if err != nil { 39 | log.Fatalf("Fail to OpenFile :%v", err) 40 | } 41 | 42 | return handle 43 | } 44 | 45 | func mkDir() { 46 | dir, _ := os.Getwd() 47 | err := os.MkdirAll(dir + "/" + getLogFilePath(), os.ModePerm) 48 | if err != nil { 49 | panic(err) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /web/src/pages/welcome/info.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {Menu} from "antd"; 3 | import Basic from './Basic'; 4 | import Reset from './Reset'; 5 | import styles from './index.module.css'; 6 | 7 | class WelcomeInfo extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | selectedKeys: ['basic'] 12 | } 13 | } 14 | 15 | render() { 16 | const {selectedKeys} = this.state; 17 | return ( 18 |
19 |
20 | this.setState({selectedKeys})}> 25 | 基本设置 26 | 修改密码 27 | 28 |
29 |
30 | {selectedKeys[0] === 'basic' && } 31 | {selectedKeys[0] === 'reset' && } 32 |
33 |
34 | ) 35 | } 36 | } 37 | 38 | 39 | export default WelcomeInfo; 40 | -------------------------------------------------------------------------------- /api/pkg/file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "mime/multipart" 7 | "os" 8 | "path" 9 | ) 10 | 11 | func GetSize(f multipart.File) (int, error) { 12 | content, err := ioutil.ReadAll(f) 13 | 14 | return len(content), err 15 | } 16 | 17 | func GetExt(fileName string) string { 18 | return path.Ext(fileName) 19 | } 20 | 21 | func CheckExist(src string) bool { 22 | _, err := os.Stat(src) 23 | 24 | return os.IsNotExist(err) 25 | } 26 | 27 | func CheckPermission(src string) bool { 28 | _, err := os.Stat(src) 29 | 30 | return os.IsPermission(err) 31 | } 32 | 33 | func IsNotExistMkDir(src string) error { 34 | e := CheckExist(src) 35 | if e == true { 36 | if err := MkDir(src); err != nil { 37 | return err 38 | } 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func MkDir(src string) error { 45 | err := os.MkdirAll(src, os.ModePerm) 46 | fmt.Println(err) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | return nil 52 | } 53 | 54 | func Open(name string, flag int, perm os.FileMode) (*os.File, error) { 55 | f, err := os.OpenFile(name, flag, perm) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return f, nil 61 | } 62 | -------------------------------------------------------------------------------- /api/files/AppInit/BackendJar/jarFuncs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | 4 | function start() { 5 | test -f $APP_LOG && > $APP_LOG 6 | test -f $GC_LOG && > $GC_LOG 7 | success_flag="(?:Server startup|Dubbo service server started)" 8 | failed_flag="(?:Exception)" 9 | rm -rf $APP_PID 10 | /bin/su -c "$APP_BASE/bin/run.sh" tomcat 11 | sleep 2 12 | if [ -s $APP_PID ]; then 13 | echo "$app Server startup" 14 | return 0 15 | else 16 | echo "$app failed to start." 17 | return 1 18 | fi 19 | } 20 | 21 | function stop() { 22 | kill $(cat $APP_PID) > /dev/null 2>&1 23 | #su -c "/bin/rm -rf $CATALINA_TMPDIR/*" tomcat 24 | sleep 5 25 | if [ -s $APP_PID ]; then 26 | kill -9 $(cat $APP_PID) > /dev/null 2>&1 27 | fi 28 | echo "$app stoped." 29 | return 0 30 | } 31 | 32 | monitor() { 33 | check_num=`ps ax -o pid,cmd|grep "$JAR_BOOTER"|grep -v grep|wc -l` 34 | if [ $check_num -eq 0 ];then 35 | start 36 | echo `date +"%F %T"` - restart. 37 | fi 38 | } 39 | 40 | function restart() { 41 | stop 42 | start 43 | } 44 | 45 | function log() { 46 | if [ -f $APP_LOG ]; then 47 | tail -5000 $APP_LOG 48 | else 49 | tail -5000 $(dirname $APP_LOG)/$(ls $(dirname $APP_LOG) | grep -v "gc" | head -1) 50 | fi 51 | } -------------------------------------------------------------------------------- /api/models/metric.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | var ( 6 | //HTTPReqDuration metric:http_request_duration_seconds 7 | HTTPReqDuration *prometheus.HistogramVec 8 | //HTTPReqTotal metric:http_request_total 9 | HTTPReqTotal *prometheus.CounterVec 10 | // 11 | //CpuTemp *prometheus.GaugeOpts 12 | ) 13 | 14 | func init() { 15 | // 监控接口请求耗时 16 | //HTTPReqDuration metric:http_request_duration_seconds 17 | // HistogramVec 是一组Histogram 18 | // 这里的"method"、"path" 都是label 19 | HTTPReqDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{ 20 | Name: "http_request_duration_seconds", 21 | Help: "The HTTP request latencies in seconds.", 22 | Buckets: nil, 23 | }, []string{"method", "path"}) 24 | 25 | 26 | // 监控接口请求次数 27 | //HTTPReqTotal metric:http_request_total 28 | // HistogramVec 是一组Histogram 29 | // 这里的"method"、"path"、"status" 都是label 30 | HTTPReqTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ 31 | Name: "http_requests_total", 32 | Help: "Total number of HTTP requests made.", 33 | }, []string{"method", "path", "status"}) 34 | 35 | // 添加prometheus性能监控指标 36 | prometheus.MustRegister(HTTPReqTotal) 37 | prometheus.MustRegister(HTTPReqDuration) 38 | 39 | //prometheus.MustRegister(CpuTemp) 40 | //prometheus.MustRegister(HdFailures) 41 | } -------------------------------------------------------------------------------- /web/public/xterm/xterm.min.css: -------------------------------------------------------------------------------- 1 | .xterm{font-feature-settings:"liga" 0;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#FFF;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm .xterm-scroll-area{visibility:hidden}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm{cursor:text}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility,.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:0.5}.xterm-underline{text-decoration:underline} -------------------------------------------------------------------------------- /web/src/utils/getTimeRangeConfig.js: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | 3 | // 时间范围配置 4 | 5 | const incomeConfig = { 6 | '0': { // 今日 7 | begin: () => { 8 | return moment().startOf('day'); 9 | }, 10 | end: () => { 11 | return moment().endOf('day'); 12 | }, 13 | type: 'HOUR', 14 | }, 15 | '1': { // 周 16 | begin: () => { 17 | return moment().subtract(7, 'days').startOf('day'); 18 | }, 19 | end: () => { 20 | return moment().endOf('day'); 21 | }, 22 | type: 'DAY', 23 | }, 24 | '2': { // 月 25 | begin: () => { 26 | return moment().subtract(1, 'month').startOf('day'); 27 | }, 28 | end: () => { 29 | return moment().endOf('day'); 30 | }, 31 | type: 'DAY', 32 | }, 33 | '3': { // 季度 34 | begin: () => { 35 | return moment().subtract(3, 'month').startOf('day'); 36 | }, 37 | end: () => { 38 | return moment().endOf('day'); 39 | }, 40 | type: 'DAY', 41 | }, 42 | '4': { // 年 43 | begin: () => { 44 | return moment().subtract(1, 'year').startOf('day'); 45 | }, 46 | end: () => { 47 | return moment().endOf('day'); 48 | }, 49 | type: 'MONTH', 50 | }, 51 | }; 52 | 53 | const getTimeRangeConfig = (config) => { 54 | return incomeConfig[config]; 55 | }; 56 | 57 | export default getTimeRangeConfig; 58 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "umi dev", 5 | "build": "umi build", 6 | "test": "umi test", 7 | "lint": "eslint --ext .js src mock tests", 8 | "precommit": "lint-staged" 9 | }, 10 | "dependencies": { 11 | "@antv/data-set": "^0.10.2", 12 | "ace-builds": "^1.4.7", 13 | "antd": "^3.12.1", 14 | "bizcharts": "^3.4.5", 15 | "dva": "^2.5.0-beta.2", 16 | "lodash": "^4.17.15", 17 | "moment": "^2.24.0", 18 | "qs": "^6.6.0", 19 | "react": "^16.7.0", 20 | "react-ace": "^8.1.0", 21 | "react-document-title": "^2.0.3", 22 | "react-dom": "^16.11.0", 23 | "react-infinite-scroller": "^1.2.4" 24 | }, 25 | "devDependencies": { 26 | "babel-eslint": "^9.0.0", 27 | "eslint": "^5.4.0", 28 | "eslint-config-umi": "^1.4.0", 29 | "eslint-plugin-flowtype": "^2.50.0", 30 | "eslint-plugin-import": "^2.14.0", 31 | "eslint-plugin-jsx-a11y": "^5.1.1", 32 | "eslint-plugin-react": "^7.11.1", 33 | "husky": "^0.14.3", 34 | "lint-staged": "^7.2.2", 35 | "react-test-renderer": "^16.7.0", 36 | "umi": "^2.13.11", 37 | "umi-plugin-react": "^1.4.0" 38 | }, 39 | "lint-staged": { 40 | "*.{js,jsx}": [ 41 | "eslint --fix", 42 | "git add" 43 | ] 44 | }, 45 | "engines": { 46 | "node": ">=8.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /api/models/email.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "gopkg.in/gomail.v2" 5 | "strconv" 6 | ) 7 | 8 | // 自定义发送邮箱 9 | func InitDialer(host, user, pass string , port int) *gomail.Dialer { 10 | return gomail.NewDialer(host, port, user, pass) 11 | } 12 | 13 | func SendEmail(mailinfo map[string]string, msg *gomail.Message) error { 14 | port, _ := strconv.Atoi(mailinfo["port"]) 15 | gd := InitDialer(mailinfo["server"], mailinfo["username"], mailinfo["password"], port) 16 | if e := gd.DialAndSend(msg); e != nil { 17 | return e 18 | } 19 | 20 | return nil 21 | } 22 | 23 | // 生成消息体 24 | func CreateMsg(mailFrom string, mailTo []string, subject string, body string) *gomail.Message{ 25 | m := gomail.NewMessage() 26 | m.SetHeader("From","Monitor" + "<" + mailFrom + ">") 27 | m.SetHeader("To", mailTo...) //发送给多个用户 28 | m.SetHeader("Subject", subject) //设置邮件主题 29 | m.SetBody("text/html", body) //设置邮件正文 30 | 31 | return m 32 | } 33 | 34 | // 生成带附件的消息体, 不支持非实时发送。 35 | func CreateMsgWithAnnex(mailFrom string, mailTo []string,subject string, body string, annex string) *gomail.Message{ 36 | m := gomail.NewMessage() 37 | m.SetHeader("From","Monitor" + "<" + mailFrom + ">") 38 | m.SetHeader("To", mailTo...) //发送给多个用户 39 | m.SetHeader("Subject", subject) //设置邮件主题 40 | m.SetBody("text/html", body) //设置邮件正文 41 | m.Attach(annex) // 设置附件 42 | 43 | return m 44 | } 45 | -------------------------------------------------------------------------------- /api/models/redis.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "api/pkg/setting" 5 | "github.com/go-redis/redis" 6 | "log" 7 | "time" 8 | ) 9 | 10 | var ( 11 | Rdb *redis.Client 12 | ) 13 | 14 | func init() { 15 | // 获取redis 配置 16 | r, err := setting.Cfg.GetSection("redis") 17 | if err != nil { 18 | log.Fatal(2, "Fail to get section 'redis': %v", err) 19 | } 20 | 21 | ins, _ := r.Key("DB").Int() 22 | ConnectRedis(r.Key("ADDRESS").String(), 23 | r.Key("PASSWD").String(), 24 | ins) 25 | 26 | // 每次服务启动清除本机ip地址缓存 27 | Rdb.Del(ServerLocalRunIpKey) 28 | } 29 | 30 | func ConnectRedis(addr string, passwd string, db int){ 31 | Rdb = redis.NewClient(&redis.Options{ 32 | Addr : addr, 33 | Password : passwd, 34 | DB : db, 35 | }) 36 | } 37 | 38 | func DelKey(key string) { 39 | Rdb.Del(key).Val() 40 | } 41 | 42 | func GetValByKey(key string) interface{} { 43 | return Rdb.Get(key).Val() 44 | } 45 | 46 | func SetValByKey(key string, val interface{}, expiration time.Duration) error{ 47 | _, err :=Rdb.Set(key, val, expiration).Result() 48 | 49 | return err 50 | } 51 | 52 | func SetValBySetKey(key string, val interface{}) error{ 53 | _, err := Rdb.SAdd(key, val).Result() 54 | 55 | return err 56 | } 57 | 58 | func CheckMemberByKey(key string, val interface{}) bool{ 59 | isMember, _ := Rdb.SIsMember(key, val).Result() 60 | return isMember 61 | } 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /web/src/services/deploy.js: -------------------------------------------------------------------------------- 1 | import { httpGet, httpPost, httpPut, httpDel } from '@/utils/request'; 2 | import { stringify } from 'qs'; 3 | 4 | export async function getDeploy(params) { 5 | return httpGet(`/admin/deploy/app?${stringify(params)}`); 6 | } 7 | 8 | export async function deployAdd(params) { 9 | return httpPost('/admin/deploy/app', params); 10 | } 11 | 12 | export async function deployEdit(params) { 13 | var id = params.id 14 | delete params.id 15 | return httpPut(`/admin/deploy/app/${id}`, params); 16 | } 17 | 18 | export async function deployDel(params) { 19 | return httpDel(`/admin/deploy/app/${params}`); 20 | } 21 | 22 | export async function deployReview(params) { 23 | return httpPut(`/admin/deploy/app/${params.id}/review`, params); 24 | } 25 | 26 | export async function getGitTag(params) { 27 | return httpGet(`/admin/deploy/app/${params}/tag`); 28 | } 29 | 30 | export async function getGitBranch(params) { 31 | return httpGet(`/admin/deploy/app/${params}/branch`); 32 | } 33 | 34 | export async function getGitCommit(params) { 35 | return httpGet(`/admin/deploy/app/${params.aid}/commit/${params.name}`); 36 | } 37 | 38 | export async function rollbackConfirm(params) { 39 | return httpPut(`/admin/undo/confirm/${params.id}`, params); 40 | } 41 | // export async function getAppVersion(params) { 42 | // return httpGet(`/admin/deploy/app/${params}/version`); 43 | // } 44 | -------------------------------------------------------------------------------- /api/pkg/logging/log.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | "fmt" 9 | ) 10 | 11 | type Level int 12 | 13 | var ( 14 | F *os.File 15 | 16 | DefaultPrefix = "" 17 | DefaultCallerDepth = 2 18 | 19 | logger *log.Logger 20 | logPrefix = "" 21 | levelFlags = []string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL"} 22 | ) 23 | 24 | const ( 25 | DEBUG Level = iota 26 | INFO 27 | WARNING 28 | ERROR 29 | FATAL 30 | ) 31 | 32 | func init() { 33 | filePath := getLogFileFullPath() 34 | F = openLogFile(filePath) 35 | 36 | logger = log.New(F, DefaultPrefix, log.LstdFlags) 37 | } 38 | 39 | func Debug(v ...interface{}) { 40 | setPrefix(DEBUG) 41 | logger.Println(v) 42 | } 43 | 44 | func Info(v ...interface{}) { 45 | setPrefix(INFO) 46 | logger.Println(v) 47 | } 48 | 49 | func Warn(v ...interface{}) { 50 | setPrefix(WARNING) 51 | logger.Println(v) 52 | } 53 | 54 | func Error(v ...interface{}) { 55 | setPrefix(ERROR) 56 | logger.Println(v) 57 | } 58 | 59 | func Fatal(v ...interface{}) { 60 | setPrefix(FATAL) 61 | logger.Fatalln(v) 62 | } 63 | 64 | func setPrefix(level Level) { 65 | _, file, line, ok := runtime.Caller(DefaultCallerDepth) 66 | if ok { 67 | logPrefix = fmt.Sprintf("[%s][%s:%d]", levelFlags[level], filepath.Base(file), line) 68 | } else { 69 | logPrefix = fmt.Sprintf("[%s]", levelFlags[level]) 70 | } 71 | 72 | logger.SetPrefix(logPrefix) 73 | } 74 | -------------------------------------------------------------------------------- /api/go.mod: -------------------------------------------------------------------------------- 1 | module api 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/360EntSecGroup-Skylar/excelize v1.4.1 7 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 8 | github.com/andelf/go-curl v0.0.0-20200630032108-fd49ff24ed97 9 | github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3 10 | github.com/gin-gonic/gin v1.6.3 11 | github.com/go-git/go-git/v5 v5.1.0 12 | github.com/go-ini/ini v1.57.0 13 | github.com/go-redis/redis v6.15.8+incompatible 14 | github.com/go-sql-driver/mysql v1.5.0 15 | github.com/google/uuid v1.1.1 16 | github.com/gorilla/websocket v1.4.2 17 | github.com/jinzhu/gorm v1.9.15 18 | github.com/json-iterator/go v1.1.10 19 | github.com/likexian/whois-go v1.7.1 20 | github.com/likexian/whois-parser-go v1.14.5 21 | github.com/pkg/sftp v1.11.0 22 | github.com/prometheus/client_golang v1.7.1 23 | github.com/robfig/cron/v3 v3.0.1 24 | github.com/sec51/convert v0.0.0-20190309075348-ebe586d87951 // indirect 25 | github.com/sec51/cryptoengine v0.0.0-20180911112225-2306d105a49e // indirect 26 | github.com/sec51/gf256 v0.0.0-20160126143050-2454accbeb9e // indirect 27 | github.com/sec51/qrcode v0.0.0-20160126144534-b7779abbcaf1 // indirect 28 | github.com/sec51/twofactor v1.0.0 29 | github.com/sirupsen/logrus v1.6.0 30 | github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e 31 | github.com/swaggo/gin-swagger v1.2.0 32 | github.com/swaggo/swag v1.6.7 33 | github.com/unknwon/com v1.0.1 34 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 35 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df 36 | ) 37 | -------------------------------------------------------------------------------- /web/src/pages/system/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from "dva"; 3 | import styles from './index.module.css'; 4 | import { Descriptions, Spin } from "antd"; 5 | import {VERSION} from "@/utils/About" 6 | 7 | @connect(({ loading, user }) => { 8 | return { 9 | settingAbout: user.settingAbout, 10 | settingAboutLoading: loading.effects['user/getSettingAbout'], 11 | } 12 | }) 13 | 14 | class About extends React.Component { 15 | constructor(props) { 16 | super(props); 17 | } 18 | 19 | componentDidMount() { 20 | const { dispatch } = this.props; 21 | dispatch({ 22 | type: 'user/getSettingAbout' 23 | }); 24 | } 25 | 26 | render() { 27 | const {settingAbout, settingAboutLoading} = this.props; 28 | return ( 29 | 30 |
关于
31 | 32 | {settingAbout['SystemInfo']} 33 | {settingAbout['Golangversion']} 34 | {settingAbout['GinVersion']} 35 | {VERSION} 36 | 37 | https://spug.dev 38 | 39 | 40 |
41 | ) 42 | } 43 | } 44 | 45 | export default About 46 | -------------------------------------------------------------------------------- /api/controller/admin/notify.go: -------------------------------------------------------------------------------- 1 | package admin 2 | 3 | import ( 4 | "api/middleware" 5 | "api/models" 6 | "api/pkg/util" 7 | "github.com/gin-gonic/gin" 8 | "strings" 9 | ) 10 | 11 | type NotifyResource struct { 12 | Ids string `form:"ids"` 13 | } 14 | 15 | // @Tags 通知管理 16 | // @Description 通知列表 17 | // @Summary 通知列表 18 | // @Produce json 19 | // @Param Authorization header string true "token" 20 | // @Success 200 {string} string {"code": 200, "message": "", "data": {}} 21 | // @Failure 500 {string} string {"code": 500, "message": "", "data": {}} 22 | // @Router /admin/notify [get] 23 | func GetNotify(c *gin.Context) { 24 | var notify []models.Notify 25 | data := make(map[string]interface{}) 26 | 27 | models.DB.Model(&models.Notify{}).Where("unread=1").Find(¬ify) 28 | data["lists"] = notify 29 | 30 | util.JsonRespond(200, "", data, c) 31 | } 32 | 33 | func PatchNotify(c *gin.Context) { 34 | if !middleware.PermissionCheckMiddleware(c,"notify-read") { 35 | util.JsonRespond(403, "请求资源被拒绝", "", c) 36 | return 37 | } 38 | 39 | var data NotifyResource 40 | //var notify models.Notify 41 | 42 | e := c.BindJSON(&data) 43 | if e != nil { 44 | util.JsonRespond(500, "Invalid Patch Notify Data", "", c) 45 | return 46 | } 47 | 48 | arr := strings.Split(data.Ids, ",") 49 | if len(arr) > 1 { 50 | e := models.DB.Table("notify").Where("id IN (?)", arr).Updates(map[string]interface{}{"unread": 0}).Error 51 | if e != nil { 52 | util.JsonRespond(500, e.Error(), "", c) 53 | } 54 | } else { 55 | e := models.DB.Table("notify").Where("id = ?", data.Ids).Updates(map[string]interface{}{"unread": 0}).Error 56 | if e != nil { 57 | util.JsonRespond(500, e.Error(), "", c) 58 | } 59 | } 60 | 61 | util.JsonRespond(200, "", "", c) 62 | } 63 | 64 | -------------------------------------------------------------------------------- /web/src/services/host.js: -------------------------------------------------------------------------------- 1 | import { httpGet, httpPost, httpPut, httpDel } from '@/utils/request'; 2 | import { stringify } from 'qs'; 3 | 4 | export async function getHostRole(params) { 5 | return httpGet(`/admin/host/role?${stringify(params)}`); 6 | } 7 | 8 | export async function hostRoleAdd(params) { 9 | return httpPost('/admin/host/role', params); 10 | } 11 | 12 | export async function hostRoleEdit(params) { 13 | var id = params.id 14 | delete params.id 15 | return httpPut(`/admin/host/role/${id}`, params); 16 | } 17 | 18 | export async function hostRoleDel(params) { 19 | return httpDel(`/admin/host/role/${params}`); 20 | } 21 | 22 | export async function getHost(params) { 23 | return httpGet(`/admin/host?${stringify(params)}`); 24 | } 25 | 26 | export async function hostAdd(params) { 27 | return httpPost('/admin/host', params); 28 | } 29 | 30 | export async function hostEdit(params) { 31 | var id = params.id 32 | delete params.id 33 | return httpPut(`/admin/hosts/${id}`, params); 34 | } 35 | 36 | export async function hostDel(params) { 37 | return httpDel(`/admin/hosts/${params}`); 38 | } 39 | 40 | 41 | export async function getHostApp(params) { 42 | return httpGet(`/admin/host/app?${stringify(params)}`); 43 | } 44 | 45 | export async function hostAppAdd(params) { 46 | return httpPost('/admin/host/app', params); 47 | } 48 | 49 | export async function hostAppEdit(params) { 50 | var id = params.id 51 | delete params.id 52 | return httpPut(`/admin/host/app/${id}`, params); 53 | } 54 | 55 | export async function hostAppDel(params) { 56 | return httpDel(`/admin/host/app/${params}`); 57 | } 58 | 59 | export async function getHostByAppId(params) { 60 | return httpGet(`/admin/host/appid?${stringify(params)}`); 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /web/src/layouts/UserLayout.less: -------------------------------------------------------------------------------- 1 | @import '~antd/lib/style/themes/default.less'; 2 | 3 | .container { 4 | background-image: url("./bg.svg"); 5 | background-repeat: no-repeat; 6 | background-position: center 110px; 7 | background-size: 100%; 8 | height: 100vh; 9 | display: flex; 10 | flex-direction: column; 11 | overflow: auto; 12 | } 13 | 14 | .lang { 15 | width: 100%; 16 | height: 40px; 17 | line-height: 44px; 18 | text-align: right; 19 | :global(.ant-dropdown-trigger) { 20 | margin-right: 24px; 21 | } 22 | } 23 | 24 | .content { 25 | flex: 1; 26 | padding: 32px 0; 27 | } 28 | 29 | @media (min-width: @screen-md-min) { 30 | .container { 31 | background: '#f0f2f5'; 32 | } 33 | 34 | .content { 35 | padding: 32px 0 24px 0; 36 | } 37 | } 38 | 39 | .formContainer { 40 | width: 368px; 41 | margin: 0 auto; 42 | flex: 1; 43 | } 44 | 45 | .formContainer .tabs { 46 | margin-bottom: 10px; 47 | } 48 | 49 | .top { 50 | text-align: center; 51 | } 52 | 53 | .header { 54 | height: 44px; 55 | line-height: 44px; 56 | a { 57 | text-decoration: none; 58 | } 59 | } 60 | 61 | .logo { 62 | height: 44px; 63 | margin-right: 16px; 64 | vertical-align: top; 65 | } 66 | 67 | .title { 68 | position: relative; 69 | top: 1px; 70 | color: @heading-color; 71 | font-weight: 600; 72 | font-size: 30px; 73 | font-family: Avenir, 'Helvetica Neue', Arial, Helvetica, sans-serif; 74 | } 75 | 76 | .desc { 77 | margin-top: 12px; 78 | margin-bottom: 40px; 79 | color: @text-color-secondary; 80 | font-size: @font-size-base; 81 | } 82 | 83 | .login_form { 84 | max-width: 300px; 85 | margin: 0 auto; 86 | } 87 | .login_form_forgot { 88 | float: right; 89 | } 90 | .login_form_button { 91 | width: 100%; 92 | } 93 | -------------------------------------------------------------------------------- /api/pkg/upload/image.go: -------------------------------------------------------------------------------- 1 | package upload 2 | 3 | import ( 4 | "api/pkg/file" 5 | "api/pkg/logging" 6 | "api/pkg/setting" 7 | "api/pkg/util" 8 | "fmt" 9 | "log" 10 | "mime/multipart" 11 | "os" 12 | "path" 13 | "strings" 14 | ) 15 | 16 | func GetImageFullUrl(name string) string { 17 | return setting.AppSetting.ImagePrefixUrl + "/" + GetImagePath() + name 18 | } 19 | 20 | func GetImageName(name string) string { 21 | ext := path.Ext(name) 22 | fileName := strings.TrimSuffix(name, ext) 23 | fileName = util.EncodeMD5(fileName) 24 | 25 | return fileName + ext 26 | } 27 | 28 | func GetImagePath() string { 29 | return setting.AppSetting.ImageSavePath 30 | } 31 | 32 | func GetImageFullPath() string { 33 | return setting.AppSetting.RuntimeRootPath + GetImagePath() 34 | } 35 | 36 | func CheckImageExt(fileName string) bool { 37 | ext := file.GetExt(fileName) 38 | for _, allowExt := range setting.AppSetting.ImageAllowExts { 39 | if strings.ToUpper(allowExt) == strings.ToUpper(ext) { 40 | return true 41 | } 42 | } 43 | 44 | return false 45 | } 46 | 47 | func CheckImageSize(f multipart.File) bool { 48 | size, err := file.GetSize(f) 49 | if err != nil { 50 | log.Println(err) 51 | logging.Warn(err) 52 | return false 53 | } 54 | 55 | return size <= setting.AppSetting.ImageMaxSize 56 | } 57 | 58 | func CheckImage(src string) error { 59 | dir, err := os.Getwd() 60 | if err != nil { 61 | return fmt.Errorf("os.Getwd err: %v", err) 62 | } 63 | 64 | err = file.IsNotExistMkDir(dir + "/" + src) 65 | if err != nil { 66 | return fmt.Errorf("file.IsNotExistMkDir err: %v", err) 67 | } 68 | 69 | perm := file.CheckPermission(src) 70 | if perm == true { 71 | return fmt.Errorf("file.CheckPermission Permission denied src: %s", src) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | -------------------------------------------------------------------------------- /api/models/models.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | _ "github.com/go-sql-driver/mysql" 5 | "github.com/jinzhu/gorm" 6 | "log" 7 | "api/pkg/setting" 8 | "fmt" 9 | ) 10 | 11 | var ( 12 | DB *gorm.DB 13 | DatabaseSetting = &Database{} 14 | ) 15 | 16 | type Database struct { 17 | TYPE string 18 | USER string 19 | PASSWORD string 20 | HOST string 21 | NAME string 22 | } 23 | 24 | type Model struct { 25 | ID int `gorm:"primary_key" json:"id"` 26 | } 27 | 28 | 29 | func init() { 30 | var ( 31 | err error 32 | ) 33 | 34 | err = setting.Cfg.Section("database").MapTo(DatabaseSetting) 35 | if err != nil { 36 | log.Fatalf("Cfg.MapTo DatabaseSetting err: %v", err) 37 | } 38 | 39 | //sec, err := setting.Cfg.GetSection("database") 40 | //if err != nil { 41 | // log.Fatal(2, "Fail to get section 'database': %v", err) 42 | //} 43 | // 44 | //dbType = sec.Key("TYPE").String() 45 | //dbName = sec.Key("NAME").String() 46 | //user = sec.Key("USER").String() 47 | //password = sec.Key("PASSWORD").String() 48 | //host = sec.Key("HOST").String() 49 | 50 | //DB, err = gorm.Open(dbType, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", 51 | // user, 52 | // password, 53 | // host, 54 | // dbName)) 55 | 56 | DB, err = gorm.Open(DatabaseSetting.TYPE, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local", 57 | DatabaseSetting.USER, 58 | DatabaseSetting.PASSWORD, 59 | DatabaseSetting.HOST, 60 | DatabaseSetting.NAME)) 61 | 62 | if err != nil { 63 | log.Println(err) 64 | } 65 | 66 | gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string { 67 | return defaultTableName; 68 | } 69 | 70 | DB.SingularTable(true) 71 | DB.DB().SetMaxIdleConns(10) 72 | DB.DB().SetMaxOpenConns(1000) 73 | } 74 | 75 | func CloseDB() { 76 | defer DB.Close() 77 | } 78 | -------------------------------------------------------------------------------- /web/src/pages/deploy/Ex2Info.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment, Component} from 'react'; 2 | import {connect} from "dva" 3 | import { Link } from 'react-router-dom'; 4 | import { Modal, Form, Select, Button, Icon, Input, Col } from 'antd'; 5 | import styles from './index.module.css'; 6 | 7 | @connect(({ loading, deploy, config }) => { 8 | return { 9 | } 10 | }) 11 | 12 | class Ex2Info extends React.Component { 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | loading: true, 17 | info: {}, 18 | } 19 | } 20 | 21 | componentDidMount() { 22 | this.setState({ 23 | info: this.props.info, 24 | }) 25 | } 26 | 27 | handleSubmit = () => { 28 | this.setState({loading: true}); 29 | console.log(this.state.info); 30 | this.props.dispatch({ 31 | type: 'deploy/deployAdd', 32 | payload: this.state.info, 33 | }).then(() => { 34 | this.setState({loading: false}); 35 | this.props.onCancel(); 36 | }) 37 | }; 38 | 39 | prePage = () => { 40 | this.props.prePage(this.state.info) 41 | }; 42 | 43 | onDescInputChange = (e) => { 44 | var tmp = this.props.info; 45 | tmp['Desc'] = e.target.value; 46 | this.setState({ 47 | info: tmp, 48 | }); 49 | }; 50 | 51 | 52 | render() { 53 | const {info} = this.state; 54 | 55 | return ( 56 |
57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | ) 66 | } 67 | } 68 | 69 | export default Ex2Info 70 | -------------------------------------------------------------------------------- /web/src/pages/welcome/Basic.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button, Form, Input, message } from 'antd'; 3 | import styles from './index.module.css'; 4 | import {httpPatch} from '@/utils/request'; 5 | 6 | class Basic extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | loading: false, 11 | nickname: '', 12 | } 13 | } 14 | 15 | componentDidMount() { 16 | this.setState({ 17 | nickname: JSON.parse(sessionStorage.getItem('user')).nickname 18 | }) 19 | }; 20 | 21 | handleSubmit = () => { 22 | if (this.state.nickname == "") { 23 | message.warn("昵称不能为空") 24 | return 25 | } 26 | if (JSON.parse(sessionStorage.getItem('user')).nickname == this.state.nickname) { 27 | message.warn("昵称没有改变") 28 | return 29 | } 30 | this.setState({loading: true}) 31 | httpPatch('/admin/user', {nickname: this.state.nickname, type: "nickname"}) 32 | .then(() => { 33 | message.success('设置成功,重新登录或刷新页面后生效'); 34 | var data = JSON.parse(sessionStorage.getItem('user')) 35 | data.nickname = this.state.nickname 36 | sessionStorage.setItem('user', JSON.stringify(data)) 37 | }) 38 | .finally(() => this.setState({loading: false})) 39 | } 40 | 41 | render() { 42 | return ( 43 | 44 |
基本设置
45 |
46 | 47 | this.setState({nickname: e.target.value})}/> 48 | 49 | 50 | 51 | 52 |
53 |
54 | ) 55 | } 56 | } 57 | 58 | export default Basic -------------------------------------------------------------------------------- /web/src/components/BizCharts/DailyCard.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment, PureComponent} from "react"; 2 | import {Card, Col, Icon, Row} from "antd"; 3 | import styles from './DailyCard.less' 4 | 5 | class DailyCard extends PureComponent { 6 | render() { 7 | const {loading, title, detail, value, suffix, hRate, tRate } = this.props; 8 | let hRateComponent, tRateComponent; 9 | if (hRate) { 10 | hRateComponent = 11 | 环比  12 | {hRate >= 0 ? 13 | {hRate}% 14 | : 15 | {hRate}% 16 | } 17 |    18 | ; 19 | } 20 | if (tRate) { 21 | tRateComponent = 22 | 同比  23 | {tRate >= 0 ? 24 | {tRate}% 25 | : 26 | {tRate}% 27 | } 28 | ; 29 | } 30 | return 31 | 32 | 33 | {title}({suffix}) 34 | 35 | 36 | 37 | 38 | {detail} 39 | 40 | 41 | 42 | 43 | {value} {suffix} 44 | 45 | 46 | 47 | 48 | {hRateComponent} 49 | {tRateComponent} 50 | 51 | 52 | ; 53 | } 54 | } 55 | 56 | export default DailyCard; 57 | -------------------------------------------------------------------------------- /web/src/components/SiderMenu/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Menu, Icon, Spin, Empty} from 'antd'; 3 | import Link from 'umi/link'; 4 | 5 | const SubMenu = Menu.SubMenu; 6 | 7 | class SiderMenu extends React.Component { 8 | componentDidMount() { 9 | this.props.didMount(); 10 | } 11 | 12 | render() { 13 | const { menuData, pathname, loading } = this.props; 14 | if (loading) { 15 | return ( 16 |
17 | ); 18 | } 19 | if (!menuData || menuData.length === 0) { 20 | return ; 21 | } 22 | let menuKey; 23 | const menu = menuData.map(x => { 24 | if (x.children) { 25 | const items = x.children.map(item => { 26 | if (!menuKey) { 27 | if (pathname !== '/') { 28 | if (pathname === item.Url) { 29 | menuKey = item.id; 30 | } 31 | } 32 | } 33 | 34 | return 35 | 36 | {item.Name} 37 | 38 | ; 39 | }); 40 | return {x.Name}}> 41 | {items} 42 | ; 43 | } 44 | }); 45 | // console.log(menuKey); 46 | // let selectedKey; 47 | // if (menuKey) { 48 | // selectedKey = menuKey.toString(); 49 | // } else { 50 | // selectedKey = menuData[0].children[0].id.toString(); 51 | // } 52 | 53 | // const defaultSelectedKeys = [selectedKey]; 54 | // const defaultOpenKeys = [menuData[0].id.toString()]; 55 | 56 | return ( 57 | 61 | {menu} 62 | 63 | ); 64 | } 65 | } 66 | 67 | export default SiderMenu; 68 | -------------------------------------------------------------------------------- /web/src/pages/schedule/Record.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment, Component} from "react"; 2 | import { Modal, Table, Tag, Button } from 'antd'; 3 | import {connect} from "dva"; 4 | 5 | const colorsStatus = [ 6 | {id: 0, name: '成功', color: 'green'}, 7 | {id: 1, name: '异常', color: 'red'}, 8 | ]; 9 | 10 | @connect(({ loading, schedule }) => { 11 | return { 12 | scheduleHisList: schedule.scheduleHisList, 13 | scheduleHisLoading: loading.effects['schedule/getScheduleHis'], 14 | } 15 | }) 16 | 17 | class Record extends React.Component { 18 | constructor(props) { 19 | super(props); 20 | } 21 | 22 | componentDidMount() { 23 | const { dispatch } = this.props; 24 | dispatch({ 25 | type: 'schedule/getScheduleHis', 26 | payload: { 27 | id: this.props.info.id, 28 | pagesize: 100, 29 | } 30 | }); 31 | } 32 | 33 | columns = [{ 34 | title: '执行时间', 35 | dataIndex: 'RunTime' 36 | }, { 37 | title: '执行状态', 38 | dataIndex: 'Status', 39 | 'render': Status => colorsStatus.map(x => { 40 | if (Status == x.id) { 41 | return {x.name} 42 | } 43 | }) 44 | }, { 45 | title: '执行主机', 46 | dataIndex: 'HostId', 47 | 'render': HostId => this.props.hostList.map(x => { 48 | if (HostId == x.id) { 49 | return {x.Name} 50 | } 51 | }) 52 | }, { 53 | title: '操作', 54 | render: info => 55 | }]; 56 | 57 | render() { 58 | const {scheduleHisList, scheduleHisLoading } = this.props; 59 | return ( 60 | 67 | 68 | 69 | ) 70 | } 71 | } 72 | 73 | export default Record -------------------------------------------------------------------------------- /web/src/pages/config/AddSelect.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Card, Icon } from 'antd'; 3 | import styles from './index.module.css'; 4 | 5 | class AddSelect extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | switchExt1 = () => { 11 | this.props.ext1Visible 12 | }; 13 | 14 | switchExt2 = () => { 15 | this.props.ext2Visible 16 | }; 17 | 18 | render() { 19 | const modalStyle = { 20 | display: 'flex', 21 | justifyContent: 'space-around', 22 | backgroundColor: 'rgba(240, 242, 245, 1)', 23 | padding: '80px 0' 24 | }; 25 | 26 | return ( 27 | 35 | 39 |
40 | 41 |
42 |
43 |
常规发布
44 |
45 | 由发布平台 来控制发布的主流程,你可以通过添加钩子脚本来执行额外的自定义操作。 46 |
47 |
48 |
49 | 53 |
54 | 55 |
56 |
57 |
自定义发布
58 |
59 | 你可以完全自己定义发布的所有流程和操作,发布平台 负责按顺序依次执行你记录的动作。 60 |
61 |
62 |
63 |
64 | ) 65 | } 66 | } 67 | 68 | export default AddSelect 69 | -------------------------------------------------------------------------------- /web/src/pages/deploy/Approve.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Form, Input, Switch, message } from 'antd'; 3 | 4 | class Approve extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { 8 | loading: false, 9 | } 10 | } 11 | 12 | handleSubmit = () => { 13 | const { dispatch, form: { validateFields } } = this.props; 14 | validateFields((err, values) => { 15 | if (!err) { 16 | this.setState({loading: true}); 17 | values.IsPass = values.IsPass ? 1 : 0 18 | values.id = this.props.id 19 | dispatch({ 20 | type: 'deploy/deployReview', 21 | payload: values, 22 | }).then(() => { 23 | this.setState({loading: false}); 24 | this.props.approveCanael() 25 | }) 26 | } 27 | }); 28 | }; 29 | 30 | render() { 31 | const {getFieldDecorator, getFieldValue} = this.props.form; 32 | return ( 33 | 41 |
42 | 43 | {getFieldDecorator('IsPass', { 44 | initialValue: true, 45 | valuePropName: "checked", 46 | rules: [{ required: true }], 47 | })( 48 | 49 | )} 50 | 51 | 52 | {getFieldDecorator('Reason', { 53 | rules: [{ required: getFieldValue('IsPass') == false }], 54 | })( 55 | 56 | )} 57 | 58 | 59 |
60 | ) 61 | } 62 | } 63 | 64 | export default Form.create()(Approve) 65 | -------------------------------------------------------------------------------- /web/src/pages/system/login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { 4 | Form, Icon, Input, Button, Checkbox 5 | } from 'antd'; 6 | 7 | import styles from './login.css'; 8 | 9 | @connect(({ loading }) => { 10 | return { 11 | loading: loading.global, 12 | } 13 | }) 14 | class NormalLoginForm extends React.Component { 15 | handleSubmit = (e) => { 16 | e.preventDefault(); 17 | const { dispatch, form: { validateFields } } = this.props; 18 | validateFields((err, values) => { 19 | if (!err) { 20 | dispatch({ 21 | type: 'user/login', 22 | payload: values, 23 | }); 24 | } 25 | }); 26 | } 27 | 28 | render() { 29 | const { getFieldDecorator } = this.props.form; 30 | // const {loginType} = this.props.state; 31 | // console.log(loginType) 32 | return ( 33 | 34 | 35 | {getFieldDecorator('username', { 36 | rules: [{ required: true, message: '请输入用户名' }], 37 | })( 38 | } placeholder="用户名" /> 39 | )} 40 | 41 | 42 | {getFieldDecorator('password', { 43 | rules: [{ required: true, message: '请输入密码' }], 44 | })( 45 | } type="password" placeholder="密码" /> 46 | )} 47 | 48 | 49 | {getFieldDecorator('remember', { 50 | valuePropName: 'checked', 51 | initialValue: true, 52 | })( 53 | 记住用户名 54 | )} 55 | {/* Forgot password */} 56 | 57 | 60 | {/* Or register now! */} 61 | 62 | 63 | ); 64 | } 65 | } 66 | 67 | export default Form.create()(NormalLoginForm); 68 | -------------------------------------------------------------------------------- /web/mock/statistic.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '/api/v1/statistic/summary': function (req, res) { 3 | setTimeout(() => { 4 | res.json({ 5 | code: 0, 6 | data: { 7 | visit: 124345, 8 | visitCompare: -0.1, 9 | temple: 28, 10 | templeCompare: 0.26, 11 | light: 680, 12 | lightCompare: 0.26, 13 | }, 14 | }); 15 | }, 500); 16 | }, 17 | '/api/v1/statistic/income': function (req, res) { 18 | const { begin, end, type } = req.query; 19 | let data = null; 20 | if (type === 'hour') { 21 | data = [ 22 | { date: '00:00', value: 0 }, 23 | { date: '02:00', value: 0 }, 24 | { date: '04:00', value: 0 }, 25 | { date: '06:00', value: 0 }, 26 | { date: '08:00', value: 0 }, 27 | { date: '10:00', value: 666 }, 28 | { date: '12:00', value: 19.9 }, 29 | { date: '14:00', value: 30 }, 30 | { date: '16:00', value: 999 }, 31 | { date: '18:00', value: 33 }, 32 | { date: '20:00', value: 0 }, 33 | { date: '22:00', value: 0 }, 34 | { date: '24:00', value: 0 }, 35 | ]; 36 | } else if (type === 'day') { 37 | data = [ 38 | { date: '03-06', value: 328 }, 39 | { date: '03-07', value: 122 }, 40 | { date: '03-08', value: 222 }, 41 | { date: '03-09', value: 599 }, 42 | { date: '03-10', value: 1200 }, 43 | { date: '03-11', value: 666 }, 44 | { date: '03-12', value: 999 }, 45 | ]; 46 | } else if (type === 'month') { 47 | data = [ 48 | { date: '19年1月', value: 328 }, 49 | { date: '19年2月', value: 122 }, 50 | { date: '19年3月', value: 222 }, 51 | { date: '19年4月', value: 599 }, 52 | { date: '19年5月', value: 1200 }, 53 | { date: '19年6月', value: 666 }, 54 | { date: '19年7月', value: 999 }, 55 | { date: '19年8月', value: 328 }, 56 | { date: '19年9月', value: 122 }, 57 | { date: '19年10月', value: 222 }, 58 | { date: '19年11月', value: 599 }, 59 | { date: '19年12月', value: 1200 }, 60 | ]; 61 | } 62 | setTimeout(() => { 63 | res.json({ 64 | code: 0, 65 | data: data, 66 | }); 67 | }, 500); 68 | }, 69 | }; 70 | -------------------------------------------------------------------------------- /web/src/pages/config/Ext2Form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Steps } from 'antd'; 3 | import styles from './index.module.css'; 4 | import Setup1 from './Ext2Setup1'; 5 | import Setup2 from './Ext2Setup2'; 6 | import Setup3 from './Ext2Setup3'; 7 | 8 | 9 | class Ext2Form extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | page: 0, 14 | info:{}, 15 | } 16 | } 17 | 18 | componentDidMount() { 19 | const id=this.props.id 20 | this.setState({ 21 | info: this.props.info 22 | }) 23 | }; 24 | 25 | handler = (values) => { 26 | this.setState({ 27 | info: values, 28 | page: this.state.page + 1, 29 | }) 30 | } 31 | 32 | prehandler = (values) => { 33 | this.setState({ 34 | page: this.state.page - 1, 35 | }) 36 | } 37 | 38 | render() { 39 | return ( 40 | 0? '编辑自定义发布' : '新建自定义发布'} 45 | onCancel={this.props.cancelExt2Visible} 46 | footer={null}> 47 | 48 | 49 | 50 | 51 | 52 | {this.state.page === 0 && 53 | 58 | } 59 | {this.state.page === 1 && 60 | 67 | } 68 | {this.state.page === 2 && 69 | 76 | } 77 | 78 | ) 79 | } 80 | } 81 | 82 | export default Ext2Form -------------------------------------------------------------------------------- /api/manage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/jinzhu/gorm" 7 | "os" 8 | "api/models" 9 | "api/pkg/util" 10 | ) 11 | 12 | 13 | var ( 14 | h bool 15 | c string 16 | Passwd string 17 | Check string 18 | ) 19 | 20 | func init() { 21 | flag.BoolVar(&h, "h", false, "this help") 22 | 23 | flag.StringVar(&c, "c", "", "create_admin : 创建管理员账户, enable_admin : 启用管理员账户") 24 | 25 | // 改变默认的 Usage 26 | flag.Usage = usage 27 | } 28 | 29 | func usage() { 30 | fmt.Fprintf(os.Stderr, ` 31 | Usage: progarm [-h] [-c do some work] 32 | 33 | Options: 34 | `) 35 | flag.PrintDefaults() 36 | } 37 | 38 | func main() { 39 | flag.Parse() 40 | 41 | if h { 42 | flag.Usage() 43 | return 44 | } 45 | 46 | if len(os.Args) == 1 { 47 | flag.Usage() 48 | return 49 | } 50 | 51 | switch { 52 | case c == "create_admin": 53 | CreateAdmin() 54 | 55 | case c == "enable_admin": 56 | EnableAdmin() 57 | 58 | default: 59 | flag.Usage() 60 | } 61 | } 62 | 63 | func CreateAdmin() { 64 | var user models.User 65 | 66 | //检查 admin 用户是否存在 67 | err := models.DB.Where("name = ?", "admin").First(&user).Error 68 | if err == gorm.ErrRecordNotFound { 69 | fmt.Printf("Please enter password for admin : ") 70 | fmt.Scanln(&Passwd) 71 | // 新增用户 72 | user.Name = "admin" 73 | user.PasswordHash, _ = util.HashPassword(Passwd) 74 | user.Nickname = "admin" 75 | user.IsSupper = 1 76 | user.IsActive = 1 77 | user.TwoFactor = 0 78 | 79 | if e := models.DB.Create(&user).Error; e != nil { 80 | panic(e) 81 | } 82 | } 83 | 84 | if user.Name == "admin" { 85 | fmt.Printf("已存在管理员账户admin,需要重置密码[y|n]? : ") 86 | fmt.Scanln(&Check) 87 | 88 | if Check == "y" { 89 | fmt.Printf("Please enter password for admin : ") 90 | fmt.Scanln(&Passwd) 91 | passwdhash, _ := util.HashPassword(Passwd) 92 | e := models.DB.Model(&user).Update("password_hash", passwdhash).Error 93 | if e != nil { 94 | panic(e) 95 | } 96 | } 97 | } 98 | } 99 | 100 | func EnableAdmin() { 101 | var user models.User 102 | models.DB.Where("name = ?", "admin").First(&user) 103 | 104 | user.IsActive = 1 105 | e := models.DB.Save(&user).Error 106 | 107 | if e != nil { 108 | panic(e) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /web/public/xterm/xterm-addon-fit.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(window,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(){}return e.prototype.activate=function(e){this._terminal=e},e.prototype.dispose=function(){},e.prototype.fit=function(){var e=this.proposeDimensions();if(e&&this._terminal){var t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}},e.prototype.proposeDimensions=function(){if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement){var e=this._terminal._core,t=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(t.getPropertyValue("height")),n=Math.max(0,parseInt(t.getPropertyValue("width"))),o=window.getComputedStyle(this._terminal.element),i=r-(parseInt(o.getPropertyValue("padding-top"))+parseInt(o.getPropertyValue("padding-bottom"))),a=n-(parseInt(o.getPropertyValue("padding-right"))+parseInt(o.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth;return{cols:Math.max(2,Math.floor(a/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(i/e._renderService.dimensions.actualCellHeight))}}},e}();t.FitAddon=n}])}); 2 | //# sourceMappingURL=xterm-addon-fit.js.map -------------------------------------------------------------------------------- /web/src/models/domain.js: -------------------------------------------------------------------------------- 1 | import {getDomain, domainAdd, domainEdit, domainDel} from '@/services/domain'; 2 | import router from 'umi/router'; 3 | import { message } from 'antd'; 4 | 5 | export default { 6 | namespace: 'domain', 7 | 8 | state: { 9 | domainList: [], 10 | domainListLen: 0, 11 | domainPage: 1, 12 | domainSize: 10, 13 | }, 14 | 15 | reducers: { 16 | updateDomainPage(state, { payload }) { 17 | return { 18 | ...state, 19 | domainPage: payload.page, 20 | domainSize: payload.pageSize && payload.pageSize || 100, 21 | } 22 | }, 23 | updateDomainList(state, { payload }){ 24 | return { 25 | ...state, 26 | domainList: payload.lists, 27 | domainListLen: payload.count, 28 | } 29 | }, 30 | }, 31 | effects: { 32 | *getDomain({payload}, {call, put, select }) { 33 | if (payload) { 34 | yield put({ 35 | type: 'updateDomainPage', 36 | payload: payload, 37 | }); 38 | } 39 | const state = yield select(state => state.domain); 40 | const {domainPage, domainSize} = state; 41 | const query = { 42 | page: domainPage, 43 | pagesize: domainSize, 44 | }; 45 | const response = yield call(getDomain, query); 46 | yield put({ 47 | type: 'updateDomainList', 48 | payload: response.data, 49 | }); 50 | }, 51 | *domainAdd({ payload }, { call, put }) { 52 | const response = yield call(domainAdd, payload); 53 | if (response && response.code == 200) { 54 | yield put({ 55 | type: 'getDomain', 56 | }); 57 | } else { 58 | message.error(response.message); 59 | } 60 | }, 61 | *domainEdit({ payload }, { call, put }) { 62 | const response = yield call(domainEdit, payload); 63 | if (response && response.code == 200) { 64 | yield put({ 65 | type: 'getDomain', 66 | }); 67 | } else { 68 | message.error(response.message); 69 | } 70 | }, 71 | *domainDel({ payload }, { call, put }) { 72 | const response = yield call(domainDel, payload); 73 | if (response && response.code == 200) { 74 | yield put({ 75 | type: 'getDomain', 76 | }); 77 | } else { 78 | message.error(response.message); 79 | } 80 | }, 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /web/src/pages/config/Ext1Form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal, Steps } from 'antd'; 3 | import Setup1 from './Ext1Setup1'; 4 | import Setup2 from './Ext1Setup2'; 5 | import Setup3 from './Ext1Setup3'; 6 | import styles from './index.module.css'; 7 | 8 | class Ext1Form extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | loading: false, 13 | page: 0, 14 | info:{}, 15 | } 16 | } 17 | 18 | componentDidMount() { 19 | const id=this.props.id 20 | this.setState({ 21 | info: this.props.info, 22 | }) 23 | }; 24 | 25 | handler = (values) => { 26 | this.setState({ 27 | info: values, 28 | page: this.state.page + 1, 29 | }) 30 | } 31 | 32 | prehandler = (values) => { 33 | this.setState({ 34 | page: this.state.page - 1, 35 | }) 36 | } 37 | 38 | render() { 39 | return ( 40 | 0 ? '编辑常规发布' : '新建常规发布'} 46 | onCancel={this.props.cancelExt1Visible} 47 | footer={null}> 48 | 49 | 50 | 51 | 52 | 53 | {this.state.page === 0 && 54 | 59 | } 60 | {this.state.page === 1 && 61 | 68 | } 69 | {this.state.page === 2 && 70 | 77 | } 78 | 79 | ) 80 | } 81 | } 82 | 83 | export default Ext1Form 84 | -------------------------------------------------------------------------------- /web/src/components/BizCharts/ColumnImageTopChart.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Radio, Card, Col, Row, Spin} from "antd"; 3 | 4 | import styles from './ColumnImageTopChart.less' 5 | 6 | import { 7 | G2, 8 | Chart, 9 | Geom, 10 | Axis, 11 | Tooltip, 12 | Coord, 13 | Label, 14 | Legend, 15 | View, 16 | Guide, 17 | Shape, 18 | Facet, 19 | Util 20 | } from "bizcharts"; 21 | 22 | 23 | class ColumnImageTopChart extends React.Component { 24 | constructor(props) { 25 | super(props); 26 | this.state = { 27 | chart: null, 28 | }; 29 | } 30 | 31 | onGetG2Instance = (g2Chart) => { 32 | this.setState({ 33 | chart: g2Chart, 34 | }); 35 | }; 36 | 37 | onPlotClick = (ev) => { 38 | const point = { 39 | x: ev.x, 40 | y: ev.y 41 | }; 42 | const items = this.state.chart.getTooltipItems(point); 43 | if (items && items.length > 0) { 44 | this.props.onPlotClick(items[0]); 45 | } 46 | }; 47 | 48 | render() { 49 | const { loading, data, title, onDateChange, type } = this.props; 50 | 51 | let chart; 52 | if (data && data.length > 0) { 53 | chart = 60 | 61 | 62 | 63 | 64 | ; 65 | } else { 66 | chart = ; 67 | } 68 | 69 | return 70 | 71 | 72 | 73 | 昨日 74 | 75 | 76 | 季度 77 | 年度 78 | 79 | 80 | 81 | {loading ? 82 |
83 | : 84 | chart 85 | } 86 | ; 87 | } 88 | } 89 | 90 | export default ColumnImageTopChart; 91 | 92 | -------------------------------------------------------------------------------- /web/mock/menu.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '/api/v1/menu': function (req, res) { 3 | setTimeout(() => { 4 | res.json({ 5 | data: [ 6 | { 7 | id: 1, 8 | name: '用户管理', 9 | seq: 1, 10 | }, 11 | { 12 | id: 2, 13 | name: '数据概览', 14 | seq: 2, 15 | } 16 | ] 17 | }); 18 | }, 300); 19 | }, 20 | '/api/v1/submenu': function (req, res) { 21 | setTimeout(() => { 22 | res.json({ 23 | data: [ 24 | { 25 | id: 3, 26 | parent_id: 1, 27 | name: '用户列表', 28 | url: '/user/list', 29 | icon: 'icon-text', 30 | detail: '测试1', 31 | seq: 3 32 | }, 33 | { 34 | id: 7, 35 | parent_id: 1, 36 | name: '角色管理', 37 | url: '/user/role', 38 | icon: 'icon-text', 39 | detail: '测试2', 40 | seq: 3 41 | }, 42 | { 43 | id: 4, 44 | parent_id: 1, 45 | name: '权限管理', 46 | url: '/user/permission', 47 | icon: 'icon-text', 48 | detail: '测试3', 49 | seq: 3 50 | }, 51 | { 52 | id: 5, 53 | parent_id: 2, 54 | name: '老板看板', 55 | url: '/menu/menu', 56 | icon: 'icon-text', 57 | detail: '测试4', 58 | seq: 3 59 | }, 60 | { 61 | id: 6, 62 | parent_id: 2, 63 | name: '财务看板', 64 | url: '/menu/submenu', 65 | icon: 'icon-text', 66 | detail: '测试5', 67 | seq: 3 68 | } 69 | ] 70 | }); 71 | }, 300); 72 | }, 73 | 'post /api/v1/submenu/add': function (req, res) { 74 | console.log(req.body); 75 | const { username, password } = req.body; 76 | let responseObj; 77 | responseObj = { 78 | code: 0, 79 | message: '', 80 | data: null 81 | }; 82 | setTimeout(() => { 83 | res.json(responseObj); 84 | }, 500); 85 | }, 86 | 'post /api/v1/submenu/del': function (req, res) { 87 | console.log(req.body); 88 | let responseObj; 89 | responseObj = { 90 | code: 0, 91 | message: '', 92 | data: null 93 | }; 94 | setTimeout(() => { 95 | res.json(responseObj); 96 | }, 500); 97 | }, 98 | }; -------------------------------------------------------------------------------- /web/src/models/app.js: -------------------------------------------------------------------------------- 1 | import { getMenus, getNotify, patchNotify} from '@/services/user'; 2 | import router from 'umi/router' 3 | import { message } from 'antd'; 4 | 5 | export default { 6 | namespace: 'app', 7 | 8 | state: { 9 | menu: [], 10 | user: {}, 11 | notifies: [], 12 | }, 13 | effects: { 14 | *getMenu(payload, { call, put, select }) { 15 | var id 16 | if (sessionStorage.getItem('is_supper')==1) { 17 | id = 0; 18 | } else { 19 | var temp = sessionStorage.getItem('user') 20 | id = JSON.parse(temp).rid; 21 | } 22 | 23 | const response = yield call(getMenus, id); 24 | yield put({ 25 | type: 'updateMenu', 26 | payload: response.data.lists, 27 | }); 28 | }, 29 | *getNotify(payload, { call, put, select }){ 30 | const response = yield call(getNotify); 31 | yield put({ 32 | type: 'updateNotify', 33 | payload: response.data.lists, 34 | }); 35 | }, 36 | *patchNotify({ payload }, { call, put, select }){ 37 | const response = yield call(patchNotify, payload); 38 | if (response && response.code == 200) { 39 | // yield put({ 40 | // type: 'getNotify', 41 | // }); 42 | } else { 43 | message.error(response.message); 44 | } 45 | }, 46 | }, 47 | reducers: { 48 | updateMenu(state, { payload: menu }) { 49 | return { 50 | ...state, 51 | menu, 52 | }; 53 | }, 54 | updateUser(state, { payload }) { 55 | return { 56 | ...state, 57 | user: payload, 58 | } 59 | }, 60 | updateNotify(state, { payload: notifies }) { 61 | return { 62 | ...state, 63 | notifies, 64 | }; 65 | }, 66 | }, 67 | subscriptions: { 68 | setup({ dispatch, history }) { 69 | history.listen(location => { 70 | const userJSON = sessionStorage.getItem('user'); 71 | if (userJSON) { 72 | const user = JSON.parse(userJSON); 73 | dispatch({ 74 | type: 'updateUser', 75 | payload: user, 76 | }); 77 | } 78 | const pathname = location.pathname; 79 | if (pathname !== '/user/login') { 80 | const token = sessionStorage.getItem('jwt'); 81 | const userJSON = sessionStorage.getItem('user'); 82 | if (!token || !userJSON) { 83 | sessionStorage.removeItem('jwt'); 84 | // 未登录访问 85 | router.push('/user/login'); 86 | } 87 | } 88 | }); 89 | }, 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /web/src/pages/config/index.module.css: -------------------------------------------------------------------------------- 1 | .steps { 2 | width: 520px; 3 | margin: 0 auto 30px; 4 | } 5 | 6 | .delIcon { 7 | font-size: 24px; 8 | position: relative; 9 | top: 4px 10 | } 11 | 12 | .delIcon:hover { 13 | color: #f5222d; 14 | } 15 | 16 | .deployBlock { 17 | height: 100px; 18 | margin-top: 63px; 19 | display: flex; 20 | flex-direction: column; 21 | justify-content: center; 22 | align-items: center; 23 | } 24 | 25 | .cardBlock { 26 | display: flex; 27 | justify-content: space-around; 28 | background-color: rgba(240, 242, 245, 1); 29 | padding: 50px 0; 30 | } 31 | 32 | .cardTitle { 33 | margin-bottom: 12px; 34 | font-weight: 500; 35 | font-size: 16px; 36 | color: rgba(0, 0, 0, .85); 37 | } 38 | 39 | .cardDesc { 40 | height: 64px; 41 | overflow: hidden; 42 | color: rgba(0, 0, 0, .65); 43 | } 44 | 45 | .ext2Form :global(.ant-form-item) { 46 | margin-bottom: 10px; 47 | } 48 | 49 | .delAction { 50 | cursor: pointer; 51 | position: absolute; 52 | width: 35px; 53 | padding: 10px; 54 | text-align: center; 55 | top: 32px; 56 | right: 60px; 57 | border: 1px dashed #d9d9d9; 58 | border-radius: 5px; 59 | } 60 | 61 | .delAction:hover { 62 | border-color: rgb(255, 96, 59); 63 | color: rgb(255, 96, 59); 64 | } 65 | 66 | .fullScreen { 67 | background-color: #fff; 68 | position: fixed; 69 | top: 0; 70 | left: 0; 71 | right: 0; 72 | bottom: 0; 73 | z-index: 999; 74 | } 75 | 76 | 77 | .modal { 78 | position: relative; 79 | /* text-align: center;*/ 80 | } 81 | 82 | 83 | .modal ul { 84 | color: rgba(0, 0, 0, .45); 85 | display: inline-block; 86 | line-height: 22px; 87 | margin-bottom: 4px; 88 | width: 100%; 89 | height:300px; 90 | overflow:auto; 91 | } 92 | 93 | .modaldiv { 94 | display: inline-block; 95 | line-height: 22px; 96 | margin-bottom: 4px; 97 | width: 100%; 98 | height:300px; 99 | overflow:auto; 100 | } 101 | .modaldiv div { 102 | color: rgba(0, 0, 0, .45); 103 | margin-bottom: 12px; 104 | font-weight: 500; 105 | font-size: 16px; 106 | /* display: inline-block;*/ 107 | } 108 | 109 | 110 | .modal p { 111 | font-size: 32px; 112 | line-height: 32px; 113 | margin: 0; 114 | } 115 | 116 | .modal em { 117 | background-color: #e8e8e8; 118 | position: absolute; 119 | height: 56px; 120 | width: 1px; 121 | top: 0; 122 | right: 0; 123 | } 124 | -------------------------------------------------------------------------------- /api/pkg/util/certs.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "io/ioutil" 8 | "net" 9 | "time" 10 | ) 11 | 12 | type Cert struct { 13 | CommonName string `json:"cn"` 14 | NotAfter time.Time `json:"not_after"` 15 | NotBefore time.Time `json:"not_before"` 16 | DNSNames []string `json:"dns_names"` 17 | SignatureAlgorithm string `json:"signature_algorithm"` 18 | IssuerCommonName string `json:"issuer"` 19 | Organizations []string `json:"organizations"` 20 | ExpireAfter float64 `json:"expiration"` 21 | } 22 | 23 | func getVerifiedCertificateChains(addr string, timeoutSecond time.Duration) ([][]*x509.Certificate, error) { 24 | conn, e := tls.DialWithDialer(&net.Dialer{Timeout: timeoutSecond * time.Second}, "tcp", addr, nil) 25 | if e != nil { 26 | return nil, e 27 | } 28 | defer conn.Close() 29 | 30 | chains := conn.ConnectionState().VerifiedChains 31 | return chains, nil 32 | } 33 | 34 | func ParseRemoteCertificate(addr string, timeoutSecond int) (*Cert, error) { 35 | chains, e := getVerifiedCertificateChains(addr, time.Duration(timeoutSecond)) 36 | if e != nil { 37 | return nil, e 38 | } 39 | 40 | var cert *Cert 41 | for _, chain := range chains { 42 | for _, crt := range chain { 43 | if !crt.IsCA { 44 | cert = &Cert{ 45 | CommonName: crt.Subject.CommonName, 46 | NotAfter: crt.NotAfter, 47 | NotBefore: crt.NotBefore, 48 | DNSNames: crt.DNSNames, 49 | SignatureAlgorithm: crt.SignatureAlgorithm.String(), 50 | IssuerCommonName: crt.Issuer.CommonName, 51 | Organizations: crt.Issuer.Organization, 52 | ExpireAfter: time.Until(crt.NotAfter).Seconds(), 53 | } 54 | } 55 | } 56 | } 57 | return cert, e 58 | } 59 | 60 | func ParseCertificateFile(certFile string) (*Cert, error) { 61 | b, e := ioutil.ReadFile(certFile) 62 | if e != nil { 63 | return nil, e 64 | } 65 | p, _ := pem.Decode(b) 66 | crt, e := x509.ParseCertificate(p.Bytes) 67 | if e != nil { 68 | return nil, e 69 | } 70 | return &Cert{ 71 | CommonName: crt.Subject.CommonName, 72 | NotAfter: crt.NotAfter, 73 | NotBefore: crt.NotBefore, 74 | DNSNames: crt.DNSNames, 75 | SignatureAlgorithm: crt.SignatureAlgorithm.String(), 76 | IssuerCommonName: crt.Issuer.CommonName, 77 | Organizations: crt.Issuer.Organization, 78 | ExpireAfter: time.Until(crt.NotAfter).Seconds(), 79 | }, e 80 | } 81 | 82 | func (cert *Cert) Jsonify() string { 83 | b, _ := json.Marshal(cert) 84 | return string(b) 85 | } -------------------------------------------------------------------------------- /web/src/pages/welcome/Reset.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button, Form, Input, message } from 'antd'; 3 | import styles from './index.module.css'; 4 | import {httpPatch, httpPost} from '@/utils/request'; 5 | import router from 'umi/router'; 6 | import history from '@/utils/history'; 7 | 8 | class Reset extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | loading: false, 13 | old_password: "", 14 | new_password: "", 15 | new2_password: "", 16 | } 17 | } 18 | 19 | handleSubmit = () => { 20 | if (this.state.old_password == "") { 21 | return message.error('请输入原密码') 22 | } 23 | if (this.state.new_password == "") { 24 | return message.error('请输入新密码') 25 | } 26 | if (this.state.new_password != this.state.new2_password) { 27 | return message.error('两次输入密码不一致') 28 | } 29 | if (this.state.new_password.length < 6) { 30 | return message.error('请设置至少6位的新密码') 31 | } 32 | 33 | this.setState({loading: true}) 34 | httpPatch('/admin/user', { 35 | old_password: this.state.old_password, 36 | new_password: this.state.new_password, 37 | type: "password"}).then(res => { 38 | if (res.code == 200) { 39 | message.success(res.message); 40 | httpPost('/admin/user/logout') 41 | router.push('/user/login') 42 | } else { 43 | message.error(res.message); 44 | } 45 | }).finally(() => this.setState({loading: false})) 46 | } 47 | 48 | render() { 49 | return ( 50 | 51 |
修改密码
52 |
53 | 54 | this.setState({old_password:e.target.value})}/> 55 | 56 | 57 | this.setState({new_password:e.target.value})}/> 58 | 59 | 60 | this.setState({new2_password:e.target.value})}/> 61 | 62 | 63 | 64 | 65 | 66 |
67 | ) 68 | } 69 | } 70 | 71 | export default Reset 72 | -------------------------------------------------------------------------------- /web/src/pages/deploy/Template.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment, Component} from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Modal, Form, Select, Button, Icon, Input, Col, Steps } from 'antd'; 4 | import styles from './index.module.css'; 5 | 6 | class Template extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | info: {}, 11 | } 12 | } 13 | 14 | componentDidMount() { 15 | this.setState({ 16 | info: this.props.info, 17 | }); 18 | } 19 | 20 | onNameInputChange = (e) => { 21 | var tmp = this.state.info; 22 | tmp['Name'] = e.target.value; 23 | this.setState({ 24 | info: tmp, 25 | }); 26 | }; 27 | 28 | handleTidChange = (e) => { 29 | var tmp = this.state.info; 30 | if (tmp['Tid'] != parseInt(e)) { 31 | tmp['originTid'] = tmp['Tid'] 32 | tmp['Tid'] = parseInt(e) 33 | } 34 | 35 | this.setState({ 36 | info: tmp, 37 | }); 38 | }; 39 | 40 | handler = (values) => { 41 | var tmpExtend = 1 42 | for (var i = 0; i < this.props.appTemplateList.length; i++) { 43 | if (this.props.appTemplateList[i].Dtid === values.Tid) { 44 | tmpExtend = this.props.appTemplateList[i].Extend 45 | break 46 | } 47 | } 48 | 49 | values['Extend'] = tmpExtend 50 | 51 | this.props.nextPage(values) 52 | } 53 | 54 | // nextPage = () => { 55 | // this.props.nextPage(this.state.info) 56 | // }; 57 | 58 | render() { 59 | const {info} = this.state; 60 | const {appTemplateList} = this.props; 61 | 62 | return ( 63 | 64 | 65 | 66 | 67 | 68 | 69 | 76 | 77 | 78 | 新建发布模板 79 | 80 | 81 | 82 | 86 | 87 | 88 | ) 89 | } 90 | } 91 | 92 | export default Template 93 | -------------------------------------------------------------------------------- /web/src/pages/config/AppSync.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react' 2 | import { Modal, List, Icon } from 'antd'; 3 | import styles from './index.module.css'; 4 | 5 | export default class extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.socket = null; 9 | this.state = { 10 | data: [], 11 | }; 12 | } 13 | 14 | componentDidMount() { 15 | const token = sessionStorage.getItem('jwt'); 16 | const id = this.props.id ? this.props.id: 0; 17 | const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; 18 | const hostname = window.location.hostname; 19 | const port = window.location.port; 20 | 21 | this.socket = new window.WebSocket(`${protocol}//${hostname}:${port}/admin/exec/ws/${id}/ssh/${token}`); 22 | // this.socket = new window.WebSocket(`${protocol}//127.0.0.1:9090/admin/exec/ws/${id}/ssh/${token}`); 23 | 24 | var thus = this; 25 | thus.socket.onopen = function () { 26 | var tmp = thus.state.data 27 | tmp.push("建立接连..."); 28 | thus.setState({ 29 | data : tmp, 30 | }) 31 | }; 32 | 33 | var thus = this; 34 | thus.socket.onclose = function (evt) { 35 | var tmp = thus.state.data 36 | tmp.push("End"); 37 | thus.setState({ 38 | data : tmp, 39 | }) 40 | }; 41 | 42 | thus.socket.onmessage = e => { 43 | if (e.data === 'pong') { 44 | thus.socket.send(JSON.stringify({type: "heartbeat", data: ""})); 45 | } else { 46 | var tmp = thus.state.data 47 | tmp.push(e.data); 48 | thus.setState({ 49 | data : tmp, 50 | }) 51 | } 52 | }; 53 | 54 | if (this.refs.chatoutput != null) { 55 | this.refs.chatoutput.scrollTop = this.refs.chatoutput.scrollHeight; 56 | } 57 | } 58 | 59 | componentWillUnmount() { 60 | this.socket.close() 61 | } 62 | 63 | componentDidUpdate() { 64 | if (this.refs.chatoutput != null) { 65 | this.refs.chatoutput.scrollTop = this.refs.chatoutput.scrollHeight; 66 | } 67 | } 68 | 69 | render() { 70 | const preStyle = { 71 | marginTop: 5, 72 | backgroundColor: '#eee', 73 | borderRadius: 5, 74 | padding: 10, 75 | }; 76 | 77 | return ( 78 | 87 |
88 |           
89 | {this.state.data.map((item, index) =>
{item}
)} 90 |
91 |
92 |
93 | ) 94 | } 95 | } -------------------------------------------------------------------------------- /web/src/pages/system/setting.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Menu } from 'antd'; 3 | import {connect} from "dva"; 4 | import {timeTrans, hasPermission} from "@/utils/globalTools" 5 | import BasicSetting from './BasicSetting'; 6 | import EmailSetting from './EmailSetting'; 7 | import LDAPSetting from './LDAPSetting'; 8 | import KeySetting from './KeySetting'; 9 | import About from './About'; 10 | import styles from './index.module.css'; 11 | 12 | @connect(({ loading, user }) => { 13 | return { 14 | settingList: user.settingList, 15 | settingListLoading: loading.effects['user/getSetting'], 16 | } 17 | }) 18 | 19 | class SettingPage extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | this.state = { 23 | selectedKeys: ['basic'], 24 | settings: {}, 25 | } 26 | } 27 | 28 | componentDidMount() { 29 | const { dispatch } = this.props; 30 | dispatch({ 31 | type: 'user/getSetting' 32 | }).then(()=> { 33 | var tmp = {}; 34 | for (let item of this.props.settingList) { 35 | tmp[item.Name] = item; 36 | } 37 | this.setState({ 38 | settings: tmp, 39 | }); 40 | }); 41 | } 42 | 43 | render() { 44 | const {selectedKeys} = this.state; 45 | return ( 46 |
47 |
48 | this.setState({selectedKeys})}> 53 | 基本设置 54 | LDAP设置 55 | 密钥设置 56 | 邮件服务设置 57 | 关于 58 | 59 |
60 |
61 | {selectedKeys[0] === 'basic' && } 62 | {selectedKeys[0] === 'ldap' && 63 | 67 | } 68 | {selectedKeys[0] === 'key' && 69 | 73 | } 74 | {selectedKeys[0] === 'emial' && 75 | 79 | } 80 | {selectedKeys[0] === 'about' && } 81 |
82 |
83 | ) 84 | } 85 | } 86 | 87 | export default SettingPage 88 | -------------------------------------------------------------------------------- /web/src/pages/config/Ext2Setup1.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Switch, Col, Form, Select, Button, Input } from "antd"; 4 | 5 | const tmpObj = {id: 0, Name: "关闭"} 6 | class Ext2Setup1 extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | info: {}, 11 | localRobot: [], 12 | } 13 | } 14 | 15 | componentDidMount() { 16 | console.log(this.props.robotList); 17 | // 兼容 关闭的情况 18 | var tmpArr = this.props.robotList; 19 | tmpArr.push(tmpObj); 20 | this.setState({ 21 | info: this.props.info, 22 | localRobot: tmpArr, 23 | }); 24 | } 25 | 26 | onSwitchChange = (e) => { 27 | var tmp = this.state.info; 28 | tmp['EnableCheck'] = e; 29 | this.setState({ 30 | info: tmp, 31 | }); 32 | }; 33 | 34 | 35 | onTnameInputChange = (e) => { 36 | var tmp = this.state.info; 37 | tmp['TemplateName'] = e.target.value; 38 | this.setState({ 39 | info: tmp, 40 | }); 41 | }; 42 | 43 | 44 | handleNotifyChange = (e) => { 45 | var tmp = this.state.info; 46 | tmp['NotifyId'] = parseInt(e); 47 | this.setState({ 48 | info: tmp, 49 | }); 50 | }; 51 | 52 | nextPage = () => { 53 | this.props.nextPage(this.state.info) 54 | }; 55 | 56 | 57 | render() { 58 | const {info, localRobot} = this.state; 59 | return ( 60 | 61 | 62 | 63 | 64 | 65 | 70 | 71 | 72 | 73 | 80 | 81 | 82 | 新建机器人通道 83 | 84 | 85 | 86 | 90 | 91 | 92 | ) 93 | } 94 | } 95 | 96 | export default Ext2Setup1 97 | -------------------------------------------------------------------------------- /api/pkg/setting/setting.go: -------------------------------------------------------------------------------- 1 | package setting 2 | 3 | import ( 4 | "github.com/go-ini/ini" 5 | "log" 6 | "time" 7 | ) 8 | 9 | var ( 10 | Cfg *ini.File 11 | 12 | RunMode string 13 | // 14 | //HTTPPort int 15 | //ReadTimeout time.Duration 16 | //WriteTimeout time.Duration 17 | 18 | //PageSize int 19 | //JwtSecret string 20 | AppSetting = &App{} 21 | ServerSetting = &Server{} 22 | ) 23 | 24 | type App struct { 25 | JwtSecret string 26 | PageSize int 27 | RuntimeRootPath string 28 | 29 | ImagePrefixUrl string 30 | ImageSavePath string 31 | ImageMaxSize int 32 | ImageAllowExts []string 33 | 34 | LogSavePath string 35 | LogSaveName string 36 | LogFileExt string 37 | TimeFormat string 38 | 39 | GitLocalPath string 40 | IdRsaPath string 41 | SyncPath string 42 | DeployPath string 43 | GitSshKey string 44 | } 45 | 46 | type Server struct { 47 | RunMode string 48 | HttpPort int 49 | ReadTimeout time.Duration 50 | WriteTimeout time.Duration 51 | } 52 | 53 | func init() { 54 | var err error 55 | Cfg, err = ini.Load("conf/app.ini") 56 | if err != nil { 57 | log.Fatalf("Fail to parse 'conf/app.ini': %v", err) 58 | } 59 | 60 | RunMode = Cfg.Section("").Key("RUN_MODE").MustString("debug") 61 | 62 | if RunMode == "debug" { 63 | Cfg, err = ini.Load("conf/debug.ini") 64 | if err != nil { 65 | log.Fatalf("Fail to parse 'conf/debug.ini': %v", err) 66 | } 67 | } else { 68 | Cfg, err = ini.Load("conf/release.ini") 69 | if err != nil { 70 | log.Fatalf("Fail to parse 'conf/release.ini': %v", err) 71 | } 72 | } 73 | 74 | LoadServer() 75 | LoadApp() 76 | } 77 | 78 | 79 | func LoadServer() { 80 | //sec, err := Cfg.GetSection("server") 81 | //if err != nil { 82 | // log.Fatalf("Fail to get section 'server': %v", err) 83 | //} 84 | 85 | //HTTPPort = sec.Key("HTTP_PORT").MustInt(8000) 86 | //ReadTimeout = time.Duration(sec.Key("READ_TIMEOUT").MustInt(60)) * time.Second 87 | //WriteTimeout = time.Duration(sec.Key("WRITE_TIMEOUT").MustInt(60)) * time.Second 88 | 89 | err := Cfg.Section("server").MapTo(ServerSetting) 90 | if err != nil { 91 | log.Fatalf("Cfg.MapTo ServerSetting err: %v", err) 92 | } 93 | 94 | ServerSetting.ReadTimeout = ServerSetting.ReadTimeout * time.Second 95 | ServerSetting.WriteTimeout = ServerSetting.ReadTimeout * time.Second 96 | } 97 | 98 | func LoadApp() { 99 | //sec, err := Cfg.GetSection("app") 100 | //if err != nil { 101 | // log.Fatalf("Fail to get section 'app': %v", err) 102 | //} 103 | 104 | //JwtSecret = sec.Key("JWT_SECRET").MustString("!@)*#)!@U#@*!@!)") 105 | //PageSize = sec.Key("PAGE_SIZE").MustInt(10) 106 | 107 | err := Cfg.Section("app").MapTo(AppSetting) 108 | if err != nil { 109 | log.Fatalf("Cfg.MapTo AppSetting err: %v", err) 110 | } 111 | 112 | AppSetting.ImageMaxSize = AppSetting.ImageMaxSize * 1024 * 1024 113 | 114 | } 115 | -------------------------------------------------------------------------------- /web/src/components/GlobalHeader/index.js: -------------------------------------------------------------------------------- 1 | import {Layout, Menu, Icon, Dropdown, Avatar, Button, Badge, List} from 'antd'; 2 | import { Link } from 'react-router-dom'; 3 | import moment from 'moment'; 4 | import styles from './index.less' 5 | 6 | const { Header } = Layout; 7 | 8 | const GlobalHeader = ({ title, user, onMenuClick, notifies, read, loading, handleReadAll, handleRead}) => { 9 | const menu = ( 10 | 11 | 12 | 13 | 个人中心 14 | 15 | 16 | 17 | 设置 18 | 19 | 20 | 21 | 退出登录 22 | 23 | 24 | ); 25 | 26 | const notify = ( 27 | 28 | 29 | ( 35 | handleRead(e, item)}> 36 | } 39 | title={{item.Title}} 40 | description={[ 41 |
{item.Content}
, 42 |
{moment(item.CreateTime).fromNow()}
43 | ]}/> 44 |
45 | )} /> 46 | {notifies.length !== 0 && ( 47 |
handleReadAll()}>全部 已读
48 | )} 49 |
50 |
51 | ); 52 | 53 | return
54 | {title} 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | {user.headimgurl && user.headimgurl.length > 0 ? 67 | 68 | : 69 | 70 | } 71 | {user.Nickname} 72 | 73 | 74 | 75 |
; 76 | }; 77 | 78 | export default GlobalHeader; 79 | -------------------------------------------------------------------------------- /web/src/pages/schedule/Info.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment, Component} from "react"; 2 | import { Modal, Table, Tag, Spin, Card, Tabs, Form, Row, Col } from 'antd'; 3 | import moment from 'moment'; 4 | import {isEmpty, timeDatetimeTrans} from "@/utils/globalTools"; 5 | 6 | 7 | class InfoModal extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | showHostName = (id) => { 13 | for (var i = 0; i < this.props.hostList.length; i++) { 14 | if (id == this.props.hostList[i].id) { 15 | return this.props.hostList[i].Name 16 | } 17 | } 18 | }; 19 | 20 | render() { 21 | const preStyle = { 22 | marginTop: 5, 23 | backgroundColor: '#eee', 24 | borderRadius: 5, 25 | padding: 10, 26 | maxHeight: 215, 27 | }; 28 | 29 | return ( 30 | 37 | { 38 | this.props.scheduleInfo != null && 39 | 40 | 41 | 42 | 43 | {this.props.scheduleInfo.Success} 44 | 45 | 46 | 47 | 48 | {this.props.scheduleInfo.Failure} 49 | 50 | 51 | 52 | {this.props.scheduleInfo.Outputs && ( 53 | 54 | {this.props.scheduleInfo.Outputs.map((item, index) => ( 55 | {this.showHostName(item.HostId)}} 58 | > 59 |
执行时间:{timeDatetimeTrans(item.CreateTime)}({moment(item.CreateTime).fromNow()})
60 |
运行耗时: {item.RunTime}
61 |
返回状态: {item.Status}(非 0 则判定为失败)
62 |
执行输出:
{item.Output}
63 |
64 | ))} 65 |
66 | )} 67 | 68 | } 69 | { 70 | this.props.scheduleInfo == null && 71 | 72 | 73 |

任务暂无执行记录

74 |
75 |
76 | } 77 | 78 | ) 79 | } 80 | } 81 | 82 | export default Form.create()(InfoModal) 83 | -------------------------------------------------------------------------------- /web/src/pages/config/Ext2Setup2.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {connect} from "dva"; 3 | import { Form, Input, InputNumber, Select, Button, Icon, Col } from "antd"; 4 | import { Link } from 'react-router-dom'; 5 | import styles from './index.module.css'; 6 | 7 | @connect(({ loading, host }) => { 8 | return { 9 | hostListByAppId: host.hostListByAppId, 10 | } 11 | }) 12 | 13 | class Ext2Setup2 extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | info: {}, 18 | } 19 | } 20 | 21 | componentDidMount() { 22 | this.props.dispatch({ 23 | type: 'host/getHostByAppId', 24 | payload: { 25 | aid: this.props.info['Aid'], 26 | } 27 | }) 28 | 29 | this.setState({ 30 | info: this.props.info, 31 | }); 32 | } 33 | 34 | handleHostChange = (e) => { 35 | var tmp = this.state.info; 36 | tmp['HostIds'] = ""; 37 | e.map ((item, index) => { 38 | if (index > 0) { 39 | tmp['HostIds'] = tmp['HostIds'] + "," + item 40 | } else { 41 | tmp['HostIds'] += item 42 | } 43 | }); 44 | 45 | this.setState({ 46 | info: tmp, 47 | }); 48 | }; 49 | 50 | nextPage = () => { 51 | this.props.nextPage(this.state.info) 52 | }; 53 | 54 | prePage = () => { 55 | this.props.prePage(this.state.info) 56 | }; 57 | 58 | checkStatus = () => { 59 | const info = this.state.info; 60 | return info['HostIds'] != undefined && info['HostIds'] != "" 61 | }; 62 | 63 | render() { 64 | const {info, defaultValue} = this.state; 65 | const hostList = this.props.hostListByAppId; 66 | 67 | return ( 68 | 69 | 70 | 71 | 84 | 85 | 86 | 业务绑定主机 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ) 95 | } 96 | } 97 | 98 | export default Ext2Setup2 -------------------------------------------------------------------------------- /web/src/pages/deploy/SelectTemplate.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment, Component} from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { Modal, Form, Select, Button, Icon, Input, Col, Steps } from 'antd'; 4 | import styles from './index.module.css'; 5 | import Template from './Template'; 6 | import Ex1Info from './Ex1Info'; 7 | import Ex2Info from './Ex2Info'; 8 | import lds from 'lodash'; 9 | 10 | const FormItem = Form.Item; 11 | const Option = Select.Option; 12 | 13 | class SelectTemplate extends React.Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | loading: false, 18 | page: 0, 19 | extend: 1, 20 | info:{}, 21 | } 22 | } 23 | 24 | componentDidMount() { 25 | this.setState({ 26 | info: this.props.editCacheData, 27 | }) 28 | } 29 | 30 | checkExtend = (tid) => { 31 | for (var i = 0; i < this.props.appTemplateList.length; i++) { 32 | if (this.props.appTemplateList[i].Dtid === tid) { 33 | this.setState({ 34 | extend: this.props.appTemplateList[i].Extend, 35 | }) 36 | break; 37 | } 38 | } 39 | } 40 | 41 | handler = (values) => { 42 | this.setState({ 43 | info: values, 44 | extend: values.Extend, 45 | page: this.state.page + 1, 46 | }) 47 | 48 | } 49 | 50 | prehandler = (values) => { 51 | this.setState({ 52 | page: this.state.page - 1, 53 | }) 54 | } 55 | 56 | handlerCancle = () => { 57 | // return; 58 | this.props.onCancel(); 59 | 60 | } 61 | 62 | render() { 63 | const {appTemplateList} = this.props; 64 | 65 | return ( 66 | 0 ? '项目上线编辑' : '项目上线提单'} 68 | visible 69 | width={960} 70 | maskClosable={false} 71 | destroyOnClose= "true" 72 | onCancel={() => this.handlerCancle()} 73 | footer={null} 74 | > 75 | 76 | 77 | 78 | 79 | {this.state.page === 0 && 80 |