├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
24 | );
25 |
26 | const notify = (
27 |
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 |
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 |
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 |
85 | }
86 | {this.state.page === 1 && this.state.extend === 1 &&
87 |
92 | }
93 | {this.state.page === 1 && this.state.extend === 2 &&
94 |
99 | }
100 |
101 | )
102 | }
103 | }
104 |
105 | export default SelectTemplate
106 |
--------------------------------------------------------------------------------
/api/models/admin.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import "api/pkg/logging"
4 |
5 | const (
6 | // redis 角色菜单 key 前缀
7 | RoleMenuListKey = "role_menu_list_"
8 |
9 | // redis 角色权限 key 前缀
10 | RoleRermsSetKey = "role_perms_set_"
11 |
12 | // 锁定项目,一个项目同时只能允许一个执行提单或者发布
13 | GitAppOnWorking = "git_app_"
14 |
15 | // all perms key
16 | AllPermsKey = "all_perms_key"
17 |
18 | // Cron Jobs Name-EntryID Hash key
19 | CronNameEntryIdKey = "cron_name_entryid"
20 |
21 | // 本机ip地址缓存 key
22 | ServerLocalRunIpKey = "server_local_run_ipaddress"
23 |
24 | // Cron Jobs 运行时候需要限制单实例运行的情况,用于标识正在运行任务的Hash key
25 | CronJobOnRunKey = "cron_job_on_run_key"
26 |
27 | // Deploy info to redis key index
28 | DeployInfoKey = "deploy_info_index_"
29 |
30 | // WebChat AccessToken key
31 | WebChatAccessToken = "web_chat_access_token_"
32 | )
33 |
34 | // 角色
35 | type Role struct {
36 | Model
37 | Name string
38 | Desc string
39 | }
40 |
41 | // 用户
42 | type User struct {
43 | Model
44 | Rid int
45 | Name string
46 | Nickname string
47 | PasswordHash string `json:"-"`
48 | Email string
49 | Mobile string
50 | Secret string
51 | TwoFactor int
52 | IsSupper int
53 | IsActive int
54 | AccessToken string
55 | TokenExpired int64
56 | }
57 |
58 | // 菜单权限
59 | type MenuPermissions struct {
60 | Model
61 | Pid int
62 | Name string
63 | Type int
64 | Permission string
65 | Url string
66 | Icon string
67 | Desc string
68 | Children []*MenuPermissions `json:"children"`
69 | }
70 |
71 | //角色权限
72 | type RolePermissionRel struct {
73 | Model
74 | Rid int
75 | Pid int
76 | }
77 |
78 | type RoleEnvApp struct {
79 | Model
80 | Rid int
81 | Eid int
82 | AppIds string
83 | }
84 |
85 | type RoleEnvHost struct {
86 | Model
87 | Rid int
88 | Eid int
89 | HostIds string
90 | }
91 |
92 | func (u *User) ReturnPermissions() []string {
93 | var res []string
94 | if u.IsSupper != 1 {
95 | rows, err := DB.Table("menu_permissions").
96 | Select("menu_permissions.permission").
97 | Joins("left join role_permission_rel on menu_permissions.id = role_permission_rel.pid").
98 | Where("role_permission_rel.rid = ?", u.Rid).
99 | Rows()
100 |
101 | if err != nil {
102 | panic(err)
103 | }
104 |
105 | for rows.Next() {
106 | var name string
107 | if e := rows.Scan(&name); e != nil {
108 | panic(e)
109 | }
110 | res = append(res, name)
111 | }
112 | }
113 |
114 | return res
115 | }
116 |
117 | func SetRolePermToSet(key string, rid int) {
118 | var mps []MenuPermissions
119 |
120 | DB.Table("menu_permissions").
121 | Select("menu_permissions.permission").
122 | Joins("left join role_permission_rel on menu_permissions.id = role_permission_rel.pid").
123 | Where("role_permission_rel.rid = ?", rid).
124 | Find(&mps)
125 |
126 | for _, v := range mps {
127 | e := SetValBySetKey(key, v.Permission)
128 | if e != nil {
129 | logging.Error(e)
130 | }
131 | }
132 | }
133 |
134 |
--------------------------------------------------------------------------------
/web/src/services/config.js:
--------------------------------------------------------------------------------
1 | import { httpGet, httpPost, httpPut, httpDel } from '@/utils/request';
2 | import { stringify } from 'qs';
3 |
4 | export async function getConfigEnv(params) {
5 | return httpGet(`/admin/config/env?${stringify(params)}`);
6 | }
7 |
8 | export async function configEnvAdd(params) {
9 | return httpPost('/admin/config/env', params);
10 | }
11 |
12 | export async function configEnvEdit(params) {
13 | var id = params.id
14 | delete params.id
15 | return httpPut(`/admin/config/env/${id}`, params);
16 | }
17 |
18 | export async function configEnvDel(params) {
19 | return httpDel(`/admin/config/env/${params}`);
20 | }
21 |
22 | export async function getAppType(params) {
23 | return httpGet(`/admin/config/type?${stringify(params)}`);
24 | }
25 |
26 | export async function appTypeAdd(params) {
27 | return httpPost('/admin/config/type', params);
28 | }
29 |
30 | export async function appTypeEdit(params) {
31 | var id = params.id
32 | delete params.id
33 | return httpPut(`/admin/config/type/${id}`, params);
34 | }
35 |
36 | export async function appTypeDel(params) {
37 | return httpDel(`/admin/config/type/${params}`);
38 | }
39 |
40 | export async function getProject(params) {
41 | return httpGet(`/admin/config/app?${stringify(params)}`);
42 | }
43 |
44 | export async function configProjectAdd(params) {
45 | return httpPost('/admin/config/app', params);
46 | }
47 |
48 | export async function configProjectEdit(params) {
49 | var id = params.id
50 | delete params.id
51 | return httpPut(`/admin/config/app/${id}`, params);
52 | }
53 |
54 | export async function configProjectDel(params) {
55 | return httpDel(`/admin/config/app/${params}`);
56 | }
57 |
58 | export async function configProjectSync(params) {
59 | return httpGet(`/admin/config/sync/app/${params}`);
60 | }
61 |
62 | export async function getAppValue(params) {
63 | return httpGet(`/admin/config/value?${stringify(params)}`);
64 | }
65 |
66 |
67 | export async function appValueAdd(params) {
68 | return httpPost('/admin/config/value', params);
69 | }
70 |
71 | export async function appValueEdit(params) {
72 | var id = params.id
73 | delete params.id
74 | return httpPut(`/admin/config/value/${id}`, params);
75 | }
76 |
77 | export async function appValueDel(params) {
78 | return httpDel(`/admin/config/value/${params}`);
79 | }
80 |
81 | export async function getDeployExtend(params) {
82 | return httpGet(`/admin/config/deploy?${stringify(params)}`);
83 | }
84 |
85 | export async function deployExtendAdd(params) {
86 | return httpPost('/admin/config/deploy', params);
87 | }
88 |
89 | export async function deployExtendEdit(params) {
90 | var id = params.Dtid
91 | delete params.Dtid
92 | return httpPut(`/admin/config/deploy/${id}`, params);
93 | }
94 |
95 | export async function deployExtendDel(params) {
96 | return httpDel(`/admin/config/deploy/${params}`);
97 | }
98 |
99 | export async function getAppTemplate(params) {
100 | return httpGet(`/admin/config/template?${stringify(params)}`);
101 | }
102 |
--------------------------------------------------------------------------------
/api/pkg/help/helper.go:
--------------------------------------------------------------------------------
1 | package help
2 |
3 | import (
4 | "api/models"
5 | "api/pkg/logging"
6 | "api/pkg/util"
7 | "bufio"
8 | "github.com/gorilla/websocket"
9 | "golang.org/x/crypto/ssh"
10 | "os"
11 | "os/exec"
12 | "strconv"
13 | "syscall"
14 | )
15 |
16 | type Helper struct {
17 | Rdbkey string
18 | Wsconn *websocket.Conn
19 | }
20 |
21 | type Msg struct {
22 | Key string
23 | Status string
24 | Step string
25 | Data string
26 | }
27 |
28 | func (h *Helper) WsWriteMsg(key string, step int, message, status string) {
29 | var msg Msg
30 | msg.Step = strconv.Itoa(step)
31 | msg.Key = key
32 | msg.Status = "info"
33 | if status != "" {
34 | msg.Status = status
35 | }
36 |
37 | message = message + "\r\n"
38 | msg.Data = message
39 | e := h.Wsconn.WriteMessage(websocket.TextMessage, []byte(util.JSONMarshalToString(msg)))
40 | if e != nil {
41 | logging.Error(e)
42 | }
43 | }
44 |
45 | func (h *Helper) Send(message Msg) {
46 | models.Rdb.LPush(h.Rdbkey, util.JSONMarshalToString(message))
47 | }
48 |
49 | func (h *Helper) SendInfo(key, message string) {
50 | var msg Msg
51 | msg.Key = key
52 | msg.Status = "info"
53 | msg.Data = message + "\r\n"
54 | h.Send(msg)
55 | }
56 |
57 | func (h *Helper) SendError(key, message string) {
58 | message = "\r\n" + message
59 | var msg Msg
60 | msg.Key = key
61 | msg.Status = "error"
62 | msg.Data = message
63 | h.Send(msg)
64 | }
65 |
66 | func (h *Helper) SendSetup(key string, step int, data string) {
67 | var msg Msg
68 | msg.Key = key
69 | msg.Step = strconv.Itoa(step)
70 | msg.Data = data
71 | h.Send(msg)
72 | }
73 |
74 | func (h *Helper) Local(command string, step int, key string, env map[string]string) error {
75 | command = "set -e\n" + command
76 |
77 | cmd := exec.Command("/bin/sh", "-c", command)
78 | cmd.Env = os.Environ()
79 | if len(env) >0 {
80 | for k, v := range env {
81 | str := k + "=" + v
82 | cmd.Env = append(cmd.Env, str)
83 | }
84 | }
85 |
86 | out, e := cmd.StdoutPipe()
87 | cmd.Stderr = cmd.Stdout
88 |
89 | e = cmd.Start()
90 |
91 | // Create a scanner which scans stdout in a line-by-line fashion
92 | scanner := bufio.NewScanner(out)
93 |
94 | for scanner.Scan() {
95 | m := scanner.Text()
96 | h.SendInfo("local", m)
97 | h.WsWriteMsg(key, step, m, "")
98 | }
99 |
100 | if e = cmd.Wait(); e != nil {
101 | if exiterr, ok := e.(*exec.ExitError); ok {
102 | exitcode := -1
103 | if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
104 | exitcode = status.ExitStatus()
105 | }
106 |
107 | h.WsWriteMsg(key, step, "exit code : " + strconv.Itoa(exitcode), "error")
108 | h.SendError("local", "exit code : " + strconv.Itoa(exitcode))
109 |
110 | return e
111 | }
112 | }
113 |
114 | return nil
115 | }
116 |
117 | func (h *Helper) Remote(key string, step int, cli *ssh.Client, command string) error {
118 | command = "set -e\n" + command
119 |
120 | out, e := util.ExecuteCmdRemote(command, cli)
121 | if e != nil {
122 | h.WsWriteMsg(key, step, e.Error(), "error")
123 | h.SendError(key, e.Error())
124 | return e
125 | }
126 |
127 | h.SendInfo(key, out)
128 | h.WsWriteMsg(key, step, out, "")
129 |
130 | return nil
131 | }
132 |
--------------------------------------------------------------------------------
/web/src/pages/deploy/SelectApp.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { Modal, Button, Menu, Spin, Icon, Input, Tooltip } from 'antd';
4 | import styles from './index.module.css';
5 | import lds from 'lodash';
6 |
7 | class SelectApp extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | envId: 0,
12 | search: '',
13 | refs: {},
14 | envIdMap: {},
15 | }
16 | }
17 |
18 | componentDidMount() {
19 | this._initEnv();
20 | }
21 |
22 | createEnvIdMap = (res) => {
23 | var tpm = {}
24 | for (let item of res) {
25 | this.tpm[item.id] = item
26 | }
27 |
28 | this.setState({envIdMap: tpm});
29 | }
30 |
31 | _initEnv = () => {
32 | if (this.props.configEnvList.length) {
33 | this.setState({envId: this.props.configEnvList[0].id})
34 | }
35 | };
36 |
37 | render() {
38 | const {envId, envIdMap} = this.state;
39 | const {configEnvList, projectsList} = this.props;
40 |
41 | let records = projectsList.filter(x => x.EnvId === Number(envId));
42 |
43 | if (this.state.search) {
44 | records = records.filter(x => x['Name'].toLowerCase().includes(this.state.search.toLowerCase()))
45 | }
46 | return (
47 |
55 |
56 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
{lds.get(envIdMap, `${envId}.Name`)}
72 |
this.setState({search: e.target.value})}/>
77 |
78 | {records.map(item => (
79 |
80 |
86 |
87 | ))}
88 | {records.length === 0 &&
89 | 该环境下还没有可发布的应用哦,快去应用管理创建应用发布配置吧。
}
90 |
91 |
92 |
93 |
94 | )
95 | }
96 | }
97 |
98 | export default SelectApp
99 |
--------------------------------------------------------------------------------
/web/src/pages/system/KeySetting.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Alert, Button, Form, Input, Modal, message } from 'antd';
3 | import styles from './index.module.css';
4 | import lds from 'lodash';
5 |
6 | class KeySetting extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | loading: false,
11 | settings: {},
12 | }
13 | }
14 |
15 | componentDidMount() {
16 | this.setState({
17 | settings: this.props.settings
18 | })
19 | }
20 |
21 | handlePublicKey = (e) => {
22 | var tmp = this.props.settings
23 | lds.set(tmp, 'public_key.Value', e.target.value)
24 | this.setState({
25 | settings: tmp
26 | })
27 | }
28 |
29 | handlePrivateKey = (e) => {
30 | var tmp = this.props.settings
31 | lds.set(tmp, 'private_key.Value', e.target.value)
32 | this.setState({
33 | settings: tmp
34 | })
35 | }
36 |
37 | handleSubmit = () => {
38 | Modal.confirm({
39 | title: '密钥修改确认',
40 | content: 请谨慎修改密钥对,修改密钥对会让现有的主机都无法进行验证,影响与主机相关的各项功能!,
41 | onOk: () => {
42 | Modal.confirm({
43 | title: '小提示',
44 | content: 修改密钥对需要重启服务后生效,已添加的主机需要重新进行编辑验证后才可以正常连接。
,
45 | onOk: this.doModify
46 | })
47 | }
48 | })
49 | }
50 |
51 | doModify = () => {
52 | const public_key = lds.get(this.state.settings, 'public_key.Value');
53 | const private_key = lds.get(this.state.settings, 'private_key.Value');
54 | this.props.dispatch({
55 | type: 'user/settingModify',
56 | payload: {
57 | Data: [{Name: 'public_key', Value: public_key}, {Name: 'private_key', Value: private_key}],
58 | }
59 | }).finally(() => this.setState({loading: false}))
60 | }
61 |
62 | render() {
63 | const {loading, settings} = this.state;
64 | return (
65 |
66 | 密钥设置
67 |
75 |
77 | this.handlePublicKey(e)}
82 | placeholder="请输入公钥"/>
83 |
84 |
85 | this.handlePrivateKey(e)}
90 | placeholder="请输入私钥"/>
91 |
92 |
93 |
94 |
95 |
96 |
97 | )
98 | }
99 | }
100 |
101 | export default KeySetting
102 |
--------------------------------------------------------------------------------
/web/src/components/BizCharts/IncomePackageChart.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from "react";
2 | import {Chart, Geom, Tooltip, Coord, Label, Legend} from 'bizcharts';
3 | import {Card, Spin} from "antd";
4 | import DataSet from "@antv/data-set";
5 |
6 |
7 | class IncomePackageChart extends PureComponent {
8 | componentDidMount() {
9 | const e = document.createEvent("Event");
10 | e.initEvent("resize", true, true);
11 | window.dispatchEvent(e);
12 | }
13 |
14 | render() {
15 | const { loading, title, data } = this.props;
16 | const { DataView } = DataSet;
17 | const dv = new DataView();
18 | const mapedData = data.map(x => {
19 | return {
20 | ...x,
21 | packageName: (x.packagePrice / 100).toFixed(2) + '元'
22 | };
23 | });
24 | dv.source(mapedData).transform({
25 | type: "percent",
26 | field: "quantity",
27 | dimension: "packagePrice",
28 | as: "percent"
29 | });
30 | const cols = {
31 | percent: {
32 | formatter: val => {
33 | val = (val * 100).toFixed(2) + "%";
34 | return val;
35 | }
36 | }
37 | };
38 | let chart;
39 | if (!data || data.length === 0) {
40 | chart = ;
41 | } else {
42 | chart =
49 |
50 |
54 |
58 | {
65 | // percent = (percent * 100).toFixed(2) + "%";
66 | return {
67 | name: (packagePrice / 100).toFixed(2) + '元',
68 | value: quantity + '盏',
69 | };
70 | }
71 | ]}
72 | style={{
73 | lineWidth: 1,
74 | stroke: "#fff"
75 | }}
76 | >
77 |
87 | ;
88 | }
89 |
90 | return
91 | {loading ?
92 |
93 | :
94 | chart
95 | }
96 | ;
97 | }
98 | }
99 |
100 | export default IncomePackageChart;
101 |
--------------------------------------------------------------------------------
/web/src/components/BizCharts/IncomeLineChart.js:
--------------------------------------------------------------------------------
1 | import React, {PureComponent} from "react";
2 | import { Radio, Card, Col, Row, Spin, DatePicker} from "antd";
3 | import { Chart, Geom, Axis, Tooltip } from 'bizcharts';
4 | import styles from './IncomeLineChart.less'
5 |
6 | const { RangePicker } = DatePicker;
7 |
8 | class IncomeLineChart extends PureComponent {
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | chart: null,
13 | };
14 | }
15 |
16 | componentDidMount() {
17 | const e = document.createEvent("Event");
18 | e.initEvent("resize", true, true);
19 | window.dispatchEvent(e);
20 | }
21 |
22 | onGetG2Instance = (g2Chart) => {
23 | this.setState({
24 | chart: g2Chart,
25 | });
26 | };
27 |
28 | onPlotClick = (ev) => {
29 | const point = {
30 | x: ev.x,
31 | y: ev.y
32 | };
33 | const items = this.state.chart.getTooltipItems(point);
34 | if (items && items.length > 0) {
35 | this.props.onPlotClick(items[0]);
36 | }
37 | };
38 |
39 | render() {
40 | const { loading, title, data, onDateChange, onDatePick, beginDate, endDate, type } = this.props;
41 | let chart;
42 | if (data && data.length > 0) {
43 | const amountLabelConfig = {
44 | formatter(text, item, index) {
45 | const amount = parseInt(text);
46 | return (amount / 100).toString();
47 | },
48 | };
49 | chart =
50 |
51 |
52 |
53 | {
54 | return {
55 | name: '点灯总金额',
56 | title: time,
57 | value: (amount / 100).toFixed(2),
58 | };
59 | }]} />
60 | ;
61 | } else {
62 | chart = ;
63 | }
64 | let sum = 0;
65 | if (data && data.length > 0) {
66 | sum = data.map(x => x.amount).reduce((total, amount )=> total + amount);
67 | }
68 | return
69 |
70 |
71 | {(sum / 100).toFixed(2)}
72 | 元
73 |
74 |
75 |
76 |
77 |
78 | 今日
79 | 周
80 | 月
81 | 季度
82 | 年度
83 |
84 |
85 |
86 |
87 |
88 |
89 | {loading ?
90 |
91 | :
92 | chart
93 | }
94 | ;
95 | }
96 | }
97 |
98 | export default IncomeLineChart;
99 |
--------------------------------------------------------------------------------
/web/src/pages/config/Ext1Setup1.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { Switch, Col, Form, Input, Select, Button } from "antd";
4 |
5 | const tmpObj = {id: 0, Name: "关闭"}
6 | class Ext1Setup1 extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | info: {},
11 | localRobot:[],
12 | }
13 | }
14 |
15 | componentDidMount() {
16 | // 兼容 关闭的情况
17 | var tmpArr = this.props.robotList;
18 | tmpArr.push(tmpObj);
19 | this.setState({
20 | info: this.props.info,
21 | localRobot: tmpArr,
22 | });
23 | }
24 |
25 | onSwitchChange = (e) => {
26 | var tmp = this.props.info;
27 | tmp['EnableCheck'] = e;
28 | this.setState({
29 | info: tmp,
30 | });
31 | };
32 |
33 | onInputChange = (e) => {
34 | var tmp = this.props.info;
35 | tmp['RepoUrl'] = e.target.value;
36 | this.setState({
37 | info: tmp,
38 | });
39 | };
40 |
41 | onTnameInputChange = (e) => {
42 | var tmp = this.props.info;
43 | tmp['TemplateName'] = e.target.value;
44 | this.setState({
45 | info: tmp,
46 | });
47 | };
48 |
49 | onTagInputChange = (e) => {
50 | var tmp = this.props.info;
51 | tmp['Tag'] = e.target.value;
52 | this.setState({
53 | info: tmp,
54 | });
55 | };
56 |
57 |
58 | handleNotifyChange = (e) => {
59 | var tmp = this.props.info;
60 | tmp['NotifyId'] = parseInt(e);
61 | this.setState({
62 | info: tmp,
63 | });
64 | };
65 |
66 | nextPage = () => {
67 | this.props.nextPage(this.state.info)
68 | };
69 |
70 | render() {
71 | const {info, localRobot} = this.state;
72 | console.log(localRobot);
73 | return (
74 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
90 |
91 |
92 |
93 |
100 |
101 |
102 | 新建机器人通道
103 |
104 |
105 |
106 |
110 |
111 |
112 | )
113 | }
114 | }
115 |
116 | export default Ext1Setup1
--------------------------------------------------------------------------------
/web/src/pages/host/Import.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Modal, Form, Input, Upload, Icon, Button, Tooltip, Alert } from 'antd';
3 | import {httpPost} from '@/utils/request';
4 |
5 | class ComImport extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | loading: false,
10 | fileList: [],
11 | }
12 | }
13 |
14 | handleSubmit = () => {
15 | this.setState({loading: true});
16 | const formData = new FormData();
17 | formData.append('file', this.state.fileList[0]);
18 | httpPost(`/admin//host/import`, formData)
19 | .then(res => {
20 | res = res.data
21 | Modal.info({
22 | title: '导入结果',
23 | content: {res.success.length}
25 | {res['fail'].length > 0 &&
26 | {res['fail'].length}
27 | }
28 | {res['skip'].length > 0 &&
29 | {res['skip'].length}
30 | }
31 | {res['invalid'].length > 0 &&
32 | {res['invalid'].length}
33 | }
34 | {res['dbfail'].length > 0 &&
35 | {res['dbfail'].length}
36 | }
37 |
38 | })
39 | }).finally(() => {
40 | this.props.dispatch({ type: 'host/getHost',
41 | payload: {
42 | page: 1,
43 | pageSize: 10,
44 | }
45 | });
46 | this.setState({loading: false})
47 | })
48 | };
49 |
50 | beforeUpload = (file) => {
51 | this.setState({fileList: [file]});
52 | return false
53 | };
54 |
55 | onRemove = () => {
56 | this.setState({fileList: []});
57 | return false
58 | };
59 |
60 | render() {
61 | return (
62 | this.props.onCancel()}
69 | confirmLoading={this.state.loading}
70 | okButtonProps={{disabled: !this.state.fileList.length}}
71 | onOk={this.handleSubmit}>
72 |
75 |
77 | 主机导入模板.xlsx
78 |
79 |
80 |
86 |
89 |
90 |
91 |
92 |
93 | )
94 | }
95 | }
96 |
97 | export default ComImport
98 |
--------------------------------------------------------------------------------
/api/pkg/util/string.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "crypto/rand"
5 | "encoding/base32"
6 | "errors"
7 | "fmt"
8 | "strconv"
9 | )
10 |
11 | // S 字符串类型转换
12 | type S string
13 |
14 | func (s S) String() string {
15 | return string(s)
16 | }
17 |
18 | // Bytes 转换为[]byte
19 | func (s S) Bytes() []byte {
20 | return []byte(s)
21 | }
22 |
23 | // Bool 转换为bool
24 | func (s S) Bool() (bool, error) {
25 | b, err := strconv.ParseBool(s.String())
26 | if err != nil {
27 | return false, err
28 | }
29 | return b, nil
30 | }
31 |
32 | // DefaultBool 转换为bool,如果出现错误则使用默认值
33 | func (s S) DefaultBool(defaultVal bool) bool {
34 | b, err := s.Bool()
35 | if err != nil {
36 | return defaultVal
37 | }
38 | return b
39 | }
40 |
41 | // Int64 转换为int64
42 | func (s S) Int64() (int64, error) {
43 | i, err := strconv.ParseInt(s.String(), 10, 64)
44 | if err != nil {
45 | return 0, err
46 | }
47 | return i, nil
48 | }
49 |
50 | // DefaultInt64 转换为int64,如果出现错误则使用默认值
51 | func (s S) DefaultInt64(defaultVal int64) int64 {
52 | i, err := s.Int64()
53 | if err != nil {
54 | return defaultVal
55 | }
56 | return i
57 | }
58 |
59 | // Int 转换为int
60 | func (s S) Int() (int, error) {
61 | i, err := s.Int64()
62 | if err != nil {
63 | return 0, err
64 | }
65 | return int(i), nil
66 | }
67 |
68 | // DefaultInt 转换为int,如果出现错误则使用默认值
69 | func (s S) DefaultInt(defaultVal int) int {
70 | i, err := s.Int()
71 | if err != nil {
72 | return defaultVal
73 | }
74 | return i
75 | }
76 |
77 | // Uint64 转换为uint64
78 | func (s S) Uint64() (uint64, error) {
79 | i, err := strconv.ParseUint(s.String(), 10, 64)
80 | if err != nil {
81 | return 0, err
82 | }
83 | return i, nil
84 | }
85 |
86 | // DefaultUint64 转换为uint64,如果出现错误则使用默认值
87 | func (s S) DefaultUint64(defaultVal uint64) uint64 {
88 | i, err := s.Uint64()
89 | if err != nil {
90 | return defaultVal
91 | }
92 | return i
93 | }
94 |
95 | // Uint 转换为uint
96 | func (s S) Uint() (uint, error) {
97 | i, err := s.Uint64()
98 | if err != nil {
99 | return 0, err
100 | }
101 | return uint(i), nil
102 | }
103 |
104 | // DefaultUint 转换为uint,如果出现错误则使用默认值
105 | func (s S) DefaultUint(defaultVal uint) uint {
106 | i, err := s.Uint()
107 | if err != nil {
108 | return defaultVal
109 | }
110 | return uint(i)
111 | }
112 |
113 | // Float64 转换为float64
114 | func (s S) Float64() (float64, error) {
115 | f, err := strconv.ParseFloat(s.String(), 64)
116 | if err != nil {
117 | return 0, err
118 | }
119 | return f, nil
120 | }
121 |
122 | // DefaultFloat64 转换为float64,如果出现错误则使用默认值
123 | func (s S) DefaultFloat64(defaultVal float64) float64 {
124 | f, err := s.Float64()
125 | if err != nil {
126 | return defaultVal
127 | }
128 | return f
129 | }
130 |
131 | // Float32 转换为float32
132 | func (s S) Float32() (float32, error) {
133 | f, err := s.Float64()
134 | if err != nil {
135 | return 0, err
136 | }
137 | return float32(f), nil
138 | }
139 |
140 | // DefaultFloat32 转换为float32,如果出现错误则使用默认值
141 | func (s S) DefaultFloat32(defaultVal float32) float32 {
142 | f, err := s.Float32()
143 | if err != nil {
144 | return defaultVal
145 | }
146 | return f
147 | }
148 |
149 | // ToJSON 转换为JSON
150 | func (s S) ToJSON(v interface{}) error {
151 | return json.Unmarshal(s.Bytes(), v)
152 | }
153 |
154 | //
155 | func RetunRandString() (string, error) {
156 | key := make([]byte, 64)
157 | total, err := rand.Read(key)
158 | if err != nil {
159 | return "", errors.New(fmt.Sprintf("rand string failed to create because there is not enough entropy, we got only %d random bytes", total))
160 | }
161 |
162 |
163 | return base32.StdEncoding.EncodeToString(key)[0:16], nil
164 | }
--------------------------------------------------------------------------------
/web/src/models/schedule.js:
--------------------------------------------------------------------------------
1 | import {getSchedule, changeScheduleActive, scheduleAdd,
2 | scheduleEdit, scheduleDel, getScheduleHis, getScheduleInfo} from '@/services/schedule';
3 | import router from 'umi/router';
4 | import { message } from 'antd';
5 |
6 | export default {
7 | namespace: 'schedule',
8 |
9 | state: {
10 | scheduleList: [],
11 | scheduleListLen: 0,
12 | schedulePage: 1,
13 | scheduleSize: 10,
14 |
15 | scheduleHisList: [],
16 | scheduleInfo: {},
17 | },
18 |
19 | reducers: {
20 | updateSchedulePage(state, { payload }) {
21 | return {
22 | ...state,
23 | schedulePage: payload.page,
24 | scheduleSize: payload.pageSize && payload.pageSize || 100,
25 | }
26 | },
27 | updateScheduleList(state, { payload }){
28 | return {
29 | ...state,
30 | scheduleList: payload.lists,
31 | scheduleListLen: payload.count,
32 | }
33 | },
34 | updateScheduleHisList(state, { payload }){
35 | return {
36 | ...state,
37 | scheduleHisList: payload.lists,
38 | }
39 | },
40 | updateScheduleInfo(state, { payload }){
41 | return {
42 | ...state,
43 | scheduleInfo: payload.lists,
44 | }
45 | },
46 | },
47 | effects: {
48 | *getSchedule({payload}, {call, put, select }) {
49 | if (payload) {
50 | yield put({
51 | type: 'updateSchedulePage',
52 | payload: payload,
53 | });
54 | }
55 | const state = yield select(state => state.schedule);
56 | const {schedulePage, scheduleSize} = state;
57 | const query = {
58 | page: schedulePage,
59 | pagesize: scheduleSize,
60 | };
61 | const response = yield call(getSchedule, query);
62 | yield put({
63 | type: 'updateScheduleList',
64 | payload: response.data,
65 | });
66 | },
67 | *getScheduleHis({payload}, {call, put, select }) {
68 | const response = yield call(getScheduleHis, payload);
69 | yield put({
70 | type: 'updateScheduleHisList',
71 | payload: response.data,
72 | });
73 | },
74 | *getScheduleInfo({payload}, {call, put, select }) {
75 | const response = yield call(getScheduleInfo, payload);
76 | yield put({
77 | type: 'updateScheduleInfo',
78 | payload: response.data,
79 | });
80 | },
81 | *scheduleAdd({ payload }, { call, put }) {
82 | const response = yield call(scheduleAdd, payload);
83 | if (response && response.code == 200) {
84 | yield put({
85 | type: 'getSchedule',
86 | });
87 | } else {
88 | message.error(response.message);
89 | }
90 | },
91 | *scheduleEdit({ payload }, { call, put }) {
92 | const response = yield call(scheduleEdit, payload);
93 | if (response && response.code == 200) {
94 | yield put({
95 | type: 'getSchedule',
96 | });
97 | } else {
98 | message.error(response.message);
99 | }
100 | },
101 | *scheduleDel({ payload }, { call, put }) {
102 | const response = yield call(scheduleDel, payload);
103 | if (response && response.code == 200) {
104 | yield put({
105 | type: 'getSchedule',
106 | });
107 | } else {
108 | message.error(response.message);
109 | }
110 | },
111 | *changeScheduleActive({ payload }, { call, put }) {
112 | const response = yield call(changeScheduleActive, payload);
113 | if (response && response.code == 200) {
114 | yield put({
115 | type: 'getSchedule',
116 | });
117 | } else {
118 | message.error(response.message);
119 | }
120 | },
121 | }
122 | };
123 |
--------------------------------------------------------------------------------
/web/src/utils/globalTools.js:
--------------------------------------------------------------------------------
1 | import {message} from "antd";
2 |
3 | // 为url 添加前缀
4 | function urlComplement(uri){
5 | return 'http://127.0.0.1:8080/' + uri
6 | }
7 |
8 | // 时间戳 格式化
9 | function timeTrans(date){
10 | var date = new Date(date*1000);//如果date为13位不需要乘1000
11 | var Y = date.getFullYear() + '-';
12 | var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
13 | var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ';
14 | var h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
15 | var m = (date.getMinutes() <10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
16 | var s = (date.getSeconds() <10 ? '0' + date.getSeconds() : date.getSeconds());
17 | return Y+M+D+h+m+s;
18 | }
19 |
20 | // 时间戳 格式化 成年月日格式
21 | function timeDatePicker(date){
22 | var date = new Date(date*1000);//如果date为13位不需要乘1000
23 | var Y = date.getFullYear() + '-';
24 | var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
25 | var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ';
26 | return Y+M+D;
27 | }
28 |
29 | // DATETIME 格式化
30 | function timeDatetimeTrans(date){
31 | var date = new Date(date);
32 | var Y = date.getFullYear() + '-';
33 | var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
34 | var D = (date.getDate() < 10 ? '0' + (date.getDate()) : date.getDate()) + ' ';
35 | var h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':';
36 | var m = (date.getMinutes() <10 ? '0' + date.getMinutes() : date.getMinutes()) + ':';
37 | var s = (date.getSeconds() <10 ? '0' + date.getSeconds() : date.getSeconds());
38 | return Y+M+D+h+m+s;
39 | }
40 |
41 | // js对象和数组深拷贝
42 | function deepCopy(obj) {
43 | if (Array.isArray(obj)) {
44 | let result = [];
45 | for (let item of obj) {
46 | result.push(deepCopy(item))
47 | }
48 | return result
49 | } else if (typeof obj === 'object' && obj !== null) {
50 | let result = {};
51 | for (let key in obj) {
52 | if (obj.hasOwnProperty(key)) {
53 | result[key] = deepCopy(obj[key])
54 | }
55 | }
56 | return result
57 | } else {
58 | return obj
59 | }
60 | }
61 |
62 | // js 两个数组比较是否相等
63 | function compareArray(a1, a2) {
64 | if ((!a1 && a2) || (a1 && ! a2)) return false;
65 | if (a1.length !== a2.length) return false;
66 | a1.sort().every(function(value, index) {
67 | if (value !== a2.sort()[index]) return false
68 | });
69 |
70 | return true;
71 | }
72 |
73 | // 前端页面的权限判断(仅作为前端功能展示的控制,具体权限控制应在后端实现)
74 | function hasPermission(strCode) {
75 | if (sessionStorage.getItem('is_supper')) return true;
76 |
77 | let permissions = sessionStorage.getItem('permissions');
78 | if (!strCode || !permissions) return false;
79 | permissions = permissions.split(',');
80 |
81 | for (let or_item of strCode.split('|')) {
82 | if (isSubArray(permissions, or_item.split('&'))) {
83 | return true
84 | }
85 | }
86 | return false
87 | }
88 |
89 | // 数组包含关系判断
90 | export function isSubArray(parent, child) {
91 | for (let item of child) {
92 | if (!parent.includes(item.trim())) {
93 | return false
94 | }
95 | }
96 | return true
97 | }
98 |
99 | // 清理输入的命令中包含的\r符号
100 | function cleanCommand(text) {
101 | return text ? text.replace(/\r\n/g, '\n') : ''
102 | }
103 |
104 | function isEmpty(obj) {
105 | for(var prop in obj) {
106 | if(obj.hasOwnProperty(prop)) {
107 | return false;
108 | }
109 | }
110 |
111 | return JSON.stringify(obj) === JSON.stringify({});
112 | }
113 |
114 | export { urlComplement, timeTrans, timeDatetimeTrans,
115 | timeDatePicker, deepCopy, compareArray, hasPermission,
116 | isEmpty, cleanCommand};
117 |
--------------------------------------------------------------------------------
/web/src/pages/system/LDAPSetting.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './index.module.css';
3 | import {Button, Form, Input, message} from "antd";
4 | import lds from "lodash";
5 |
6 |
7 | class LDAPSetting extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.setting = JSON.parse(lds.get(this.props.settings, 'ldap_service.Value', "{}"));
11 | this.state = {
12 | loading: false,
13 | ldap_test_loading: false,
14 | }
15 | }
16 |
17 | handleSubmit = () => {
18 | const formData = [];
19 | this.props.form.validateFields((err, data) => {
20 | if (!err) {
21 | this.setState({loading: true});
22 | formData.push({name: 'ldap_service', value: JSON.stringify(data)});
23 | this.props.dispatch({
24 | type: 'user/settingModify',
25 | payload: {
26 | Data: formData,
27 | }
28 | }).finally(() => this.setState({loading: false}))
29 | }
30 | })
31 | };
32 |
33 | ldapTest = () => {
34 | this.props.form.validateFields((error, data) => {
35 | if (!error) {
36 | this.setState({ldap_test_loading: true});
37 | this.props.dispatch({
38 | type: 'user/LdapTest',
39 | payload: {
40 | payload: data,
41 | }
42 | }).then(() => {
43 | message.success('LDAP服务连接成功')
44 | }).finally(() => ()=> this.setState({ldap_test_loading: false}))
45 | }
46 | })
47 | };
48 |
49 | render() {
50 | const {getFieldDecorator} = this.props.form;
51 | return (
52 |
53 | LDAP设置
54 |
56 | {getFieldDecorator('server', {initialValue: this.setting['server'],
57 | rules: [{required: true, message: '请输入LDAP服务地址'}]})(
58 |
59 | )}
60 |
61 |
62 | {getFieldDecorator('port', {initialValue: this.setting['port'],
63 | rules: [{required: true, message: '请输入LDAP服务端口'}]})(
64 |
65 | )}
66 |
67 |
68 | {getFieldDecorator('admin_dn', {initialValue: this.setting['admin_dn'],
69 | rules: [{required: true, message: '请输入LDAP管理员DN'}]})(
70 |
71 | )}
72 |
73 |
74 | {getFieldDecorator('password', {initialValue: this.setting['password'],
75 | rules: [{required: true, message: '请输入LDAP管理员密码'}]})(
76 |
77 | )}
78 |
79 |
80 | {getFieldDecorator('rules', {initialValue: this.setting['rules'],
81 | rules: [{required: true, message: '请输入LDAP搜索规则'}]})(
82 |
83 | )}
84 |
85 |
86 | {getFieldDecorator('base_dn', {initialValue: this.setting['base_dn'],
87 | rules: [{required: true, message: '请输入LDAP基本DN'}]})(
88 |
89 | )}
90 |
91 |
92 |
94 |
95 |
96 |
97 |
98 | )
99 | }
100 | }
101 | export default Form.create()(LDAPSetting)
102 |
--------------------------------------------------------------------------------
/api/pkg/auth/jwtauth/jwt_auth.go:
--------------------------------------------------------------------------------
1 | package jwtauth
2 |
3 | import (
4 | "time"
5 | "api/pkg/auth"
6 | jwt "github.com/dgrijalva/jwt-go"
7 | )
8 |
9 | const defaultKey = "GINADMIN"
10 |
11 | var defaultOptions = options{
12 | tokenType: "Bearer",
13 | expired: 7200,
14 | signingMethod: jwt.SigningMethodHS512,
15 | signingKey: defaultKey,
16 | keyfunc: func(t *jwt.Token) (interface{}, error) {
17 | if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
18 | return nil, auth.ErrInvalidToken
19 | }
20 | return []byte(defaultKey), nil
21 | },
22 | }
23 |
24 | type options struct {
25 | signingMethod jwt.SigningMethod
26 | signingKey interface{}
27 | keyfunc jwt.Keyfunc
28 | expired int
29 | tokenType string
30 | }
31 |
32 | // Option 定义参数项
33 | type Option func(*options)
34 |
35 | // SetSigningMethod 设定签名方式
36 | func SetSigningMethod(method jwt.SigningMethod) Option {
37 | return func(o *options) {
38 | o.signingMethod = method
39 | }
40 | }
41 |
42 | // SetSigningKey 设定签名key
43 | func SetSigningKey(key interface{}) Option {
44 | return func(o *options) {
45 | o.signingKey = key
46 | }
47 | }
48 |
49 | // SetKeyfunc 设定验证key的回调函数
50 | func SetKeyfunc(keyFunc jwt.Keyfunc) Option {
51 | return func(o *options) {
52 | o.keyfunc = keyFunc
53 | }
54 | }
55 |
56 | // SetExpired 设定令牌过期时长(单位秒,默认7200)
57 | func SetExpired(expired int) Option {
58 | return func(o *options) {
59 | o.expired = expired
60 | }
61 | }
62 |
63 | // New 创建认证实例
64 | func New(store Storer, opts ...Option) *JWTAuth {
65 | o := defaultOptions
66 | for _, opt := range opts {
67 | opt(&o)
68 | }
69 |
70 | return &JWTAuth{
71 | opts: &o,
72 | }
73 | }
74 |
75 | // JWTAuth jwt认证
76 | type JWTAuth struct {
77 | opts *options
78 | store Storer
79 | }
80 |
81 | // GenerateToken 生成令牌
82 | func (a *JWTAuth) GenerateToken(userID string) (auth.TokenInfo, error) {
83 | now := time.Now()
84 | expiresAt := now.Add(time.Duration(a.opts.expired) * time.Second).Unix()
85 |
86 | token := jwt.NewWithClaims(a.opts.signingMethod, &jwt.StandardClaims{
87 | IssuedAt: now.Unix(),
88 | ExpiresAt: expiresAt,
89 | NotBefore: now.Unix(),
90 | Subject: userID,
91 | })
92 |
93 | tokenString, err := token.SignedString(a.opts.signingKey)
94 | if err != nil {
95 | return nil, err
96 | }
97 |
98 | tokenInfo := &tokenInfo{
99 | ExpiresAt: expiresAt,
100 | TokenType: a.opts.tokenType,
101 | AccessToken: tokenString,
102 | }
103 | return tokenInfo, nil
104 | }
105 |
106 | // 解析令牌
107 | func (a *JWTAuth) parseToken(tokenString string) (*jwt.StandardClaims, error) {
108 | token, _ := jwt.ParseWithClaims(tokenString, &jwt.StandardClaims{}, a.opts.keyfunc)
109 | if !token.Valid {
110 | return nil, auth.ErrInvalidToken
111 | }
112 |
113 | return token.Claims.(*jwt.StandardClaims), nil
114 | }
115 |
116 | func (a *JWTAuth) callStore(fn func(Storer) error) error {
117 | if store := a.store; store != nil {
118 | return fn(store)
119 | }
120 | return nil
121 | }
122 |
123 | // DestroyToken 销毁令牌
124 | func (a *JWTAuth) DestroyToken(tokenString string) error {
125 | claims, err := a.parseToken(tokenString)
126 | if err != nil {
127 | return err
128 | }
129 |
130 | // 如果设定了存储,则将未过期的令牌放入
131 | return a.callStore(func(store Storer) error {
132 | expired := time.Unix(claims.ExpiresAt, 0).Sub(time.Now())
133 | return store.Set(tokenString, expired)
134 | })
135 | }
136 |
137 | // ParseUserID 解析用户ID
138 | func (a *JWTAuth) ParseUserID(tokenString string) (string, error) {
139 | claims, err := a.parseToken(tokenString)
140 | if err != nil {
141 | return "", err
142 | }
143 |
144 | err = a.callStore(func(store Storer) error {
145 | exists, err := store.Check(tokenString)
146 | if err != nil {
147 | return err
148 | } else if exists {
149 | return auth.ErrInvalidToken
150 | }
151 | return nil
152 | })
153 | if err != nil {
154 | return "", err
155 | }
156 |
157 | return claims.Subject, nil
158 | }
159 |
160 | // Release 释放资源
161 | func (a *JWTAuth) Release() error {
162 | return a.callStore(func(store Storer) error {
163 | return store.Close()
164 | })
165 | }
166 |
--------------------------------------------------------------------------------
/web/src/services/user.js:
--------------------------------------------------------------------------------
1 | import {httpGet, httpPut, httpPatch, httpPost, httpDel} from '@/utils/request';
2 | import { stringify } from 'qs';
3 |
4 |
5 | export async function getNotify(params) {
6 | return httpGet(`/admin/notify`);
7 | }
8 |
9 | export async function patchNotify(params) {
10 | return httpPatch(`/admin/notify`, params);
11 | }
12 |
13 | export async function userLogin(params) {
14 | return httpPost('/admin/user/login', params);
15 | }
16 |
17 | export async function userLogout() {
18 | return httpPost('/admin/user/logout');
19 | }
20 |
21 | export async function getSetting() {
22 | return httpGet('/admin/system');
23 | }
24 |
25 | export async function getSettingAbout() {
26 | return httpGet('/admin/system/about');
27 | }
28 |
29 | export async function getMenus(params) {
30 | return httpGet(`/admin/user/perms/${params}`);
31 | }
32 |
33 | export async function getLists() {
34 | return httpGet('/admin/user');
35 | }
36 |
37 | export async function userAdd(params) {
38 | return httpPost('/admin/user', params);
39 | }
40 | export async function userEdit(params) {
41 | return httpPut(`/admin/user/${params.id}`, params);
42 | }
43 |
44 | export async function userDel(params) {
45 | return httpDel(`/admin/user/${params}`);
46 | }
47 |
48 | export async function getRoles() {
49 | return httpGet('/admin/roles');
50 | }
51 |
52 | export async function roleAdd(params) {
53 | return httpPost('/admin/roles', params);
54 | }
55 |
56 | export async function roleEdit(params) {
57 | return httpPut(`/admin/roles/${params.id}`, params);
58 | }
59 |
60 | export async function roleDel(params) {
61 | return httpDel(`/admin/roles/${params.id}`);
62 | }
63 |
64 | export async function getPermissions(params) {
65 | return httpGet(`/admin/perms?${stringify(params)}`);
66 | }
67 |
68 | export async function permAdd(params) {
69 | return httpPost('/admin/perms', params);
70 | }
71 |
72 | export async function permEdit(params) {
73 | return httpPut(`/admin/perms/${params.id}`, params);
74 | }
75 |
76 | export async function permDel(params) {
77 | return httpDel(`/admin/perms/${params}`);
78 | }
79 |
80 | export async function getAllPermissions() {
81 | return httpGet('/admin/perms/lists');
82 | }
83 |
84 | export async function getRolePerms(params) {
85 | return httpGet(`/admin/roles/${params}/permissions`);
86 | }
87 |
88 | export async function rolePermsAdd(params) {
89 | var id = params.id
90 | delete params.id
91 | return httpPost(`/admin/roles/${id}/permissions`, params);
92 | }
93 |
94 | export async function roleAppAdd(params) {
95 | var id = params.id
96 | delete params.id
97 | return httpPost(`/admin/roles/${id}/app`, params);
98 | }
99 |
100 | export async function roleHostAdd(params) {
101 | var id = params.id
102 | delete params.id
103 | return httpPost(`/admin/roles/${id}/host`, params);
104 | }
105 |
106 | export async function getAllEnvApp() {
107 | return httpGet('/admin/env/app');
108 | }
109 |
110 | export async function getAllEnvHost() {
111 | return httpGet('/admin/env/host');
112 | }
113 |
114 | export async function getRoleEnvApp(params) {
115 | return httpGet(`/admin/roles/${params}/app`);
116 | }
117 |
118 | export async function getRoleEnvHost(params) {
119 | return httpGet(`/admin/roles/${params}/host`);
120 | }
121 |
122 | export async function getRobot(params) {
123 | return httpGet(`/admin/system/robot?${stringify(params)}`);
124 | }
125 |
126 | export async function robotAdd(params) {
127 | return httpPost('/admin/system/robot', params);
128 | }
129 |
130 | export async function robotEdit(params) {
131 | return httpPut(`/admin/system/robot/${params.id}`, params);
132 | }
133 |
134 | export async function robotDel(params) {
135 | return httpDel(`/admin/system/robot/${params}`);
136 | }
137 |
138 | export async function settingModify(params) {
139 | return httpPost(`/admin/system`, params);
140 | }
141 |
142 | export async function settingMailTest(params) {
143 | return httpPost(`/admin/system/mail`, params);
144 | }
145 |
146 | export async function robotTest(params) {
147 | return httpPost(`/admin/system/robot/${params.id}`, params);
148 | }
149 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/web/src/layouts/UserLayout.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react';
2 | import { connect } from 'dva';
3 | import DocumentTitle from 'react-document-title';
4 | import GlobalFooter from '@/components/GlobalFooter';
5 | import Link from 'umi/link';
6 | import styles from './UserLayout.less';
7 | import logo from './logo.svg';
8 |
9 | import {
10 | Form, Icon, Input, Button, Checkbox, Tabs
11 | } from 'antd';
12 |
13 | @connect(({ loading }) => {
14 | return {
15 | loading: loading.global,
16 | }
17 | })
18 |
19 | class UserLayout extends Component {
20 | componentDidMount() {
21 | }
22 |
23 | state = {
24 | loginType: 'default',
25 | };
26 |
27 | handleSubmit = (e) => {
28 | e.preventDefault();
29 | const { dispatch, form: { validateFields } } = this.props;
30 | validateFields((err, values) => {
31 | values['type'] = this.state.loginType;
32 | if (!err) {
33 | dispatch({
34 | type: 'user/login',
35 | payload: values,
36 | });
37 | }
38 | });
39 | }
40 |
41 | render() {
42 | const { getFieldDecorator } = this.props.form;
43 |
44 | return (
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |

53 |
Golang Spug Demo
54 |
55 |
56 |
57 |
灵活、强大、功能全面的开源运维平台
58 |
59 |
60 |
this.setState({loginType: e})}>
61 |
62 |
63 |
64 |
66 | {getFieldDecorator('username', {
67 | rules: [{ required: true, message: '请输入用户名' }],
68 | })(
69 | } placeholder="用户名" />
70 | )}
71 |
72 |
73 | {getFieldDecorator('password', {
74 | rules: [{ required: true, message: '请输入密码' }],
75 | })(
76 | } type="password" placeholder="密码" />
77 | )}
78 |
79 |
80 | {getFieldDecorator('secret', {
81 | rules: [{ required: false, message: '请输入动态口令' }],
82 | })(
83 | } placeholder="动态口令" />
84 | )}
85 |
86 |
87 | {getFieldDecorator('remember', {
88 | valuePropName: 'checked',
89 | initialValue: true,
90 | })(
91 | 记住用户名
92 | )}
93 | {/* Forgot password */}
94 |
95 |
98 | {/* Or register now! */}
99 |
100 |
101 |
102 |
103 |
107 | Copyright 2019 By OpenSpug
108 |
109 | }
110 | />
111 |
112 |
113 | );
114 | }
115 | }
116 |
117 | export default Form.create()(UserLayout)
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/web/src/layouts/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {Icon, Layout, LocaleProvider, ConfigProvider, Modal} from 'antd';
3 | import UserLayout from './UserLayout';
4 | import Redirect from 'umi/redirect';
5 | import zhCN from 'antd/lib/locale-provider/zh_CN'
6 | import SiderMenu from '@/components/SiderMenu'
7 | import GlobalHeader from '@/components/GlobalHeader'
8 | import GlobalFooter from '@/components/GlobalFooter'
9 | import getPageTitle from '@/utils/getPageTitle'
10 | import {connect} from "dva";
11 |
12 | const { Sider, Content } = Layout;
13 |
14 | @connect(( { loading, app } ) => {
15 | return {
16 | menu: app.menu,
17 | user: app.user,
18 | notifies: app.notifies,
19 | loadingMenu: loading.effects['app/getMenu'],
20 | };
21 | })
22 | class BasicLayout extends Component {
23 | state = {
24 | loading: true,
25 | // notifies: [],
26 | read: []
27 | };
28 |
29 | fetch = () => {
30 | const { dispatch } = this.props;
31 | this.setState({loading: true});
32 | dispatch({
33 | type: 'app/getNotify'
34 | }).then(() => this.setState({read: []}))
35 | .finally(() => this.setState({loading: false}))
36 | }
37 |
38 | handleRead = (e, item) => {
39 | e.stopPropagation();
40 | if (this.state.read.indexOf(item.id) === -1) {
41 | this.state.read.push(item.id);
42 | this.setState({read: this.state.read});
43 | const { dispatch } = this.props;
44 | dispatch({
45 | type: 'app/patchNotify',
46 | payload: {ids: item.id.toString()},
47 | })
48 | }
49 | };
50 |
51 | handleReadAll = () => {
52 | const ids = this.props.notifies.map(x => x.id);
53 | this.setState({read: ids});
54 | const { dispatch } = this.props;
55 | dispatch({
56 | type: 'app/patchNotify',
57 | payload: {ids: ids.join(",")},
58 | })
59 | };
60 |
61 | menuClick = ({ key }) => {
62 | const { dispatch } = this.props;
63 | if (key === 'logout') {
64 | Modal.confirm({
65 | title: '确定要退出吗?',
66 | onOk() {
67 | dispatch({
68 | type: 'user/logout'
69 | });
70 | },
71 | });
72 | }
73 | };
74 |
75 | menuDidMount = () => {
76 | this.props.dispatch({
77 | type: 'app/getMenu',
78 | });
79 |
80 | this.fetch();
81 | this.interval = setInterval(this.fetch, 60000)
82 | };
83 |
84 | render() {
85 | const {read, loading} = this.state;
86 | const {notifies, menu, user, loadingMenu, location: { pathname } } = this.props;
87 | if (pathname === '/user/login') {
88 | // return {this.props.children}
89 | return
90 | }
91 | if (pathname === '/') {
92 | if (menu && menu.length > 0) {
93 | return ;
94 | }
95 | }
96 | return (
97 |
98 |
99 |
100 |
101 |
102 | Spug 管理平台
103 |
104 |
108 |
109 |
110 |
119 |
120 |
121 | {this.props.children}
122 |
123 |
124 |
125 |
126 |
127 |
128 | )
129 | }
130 | }
131 |
132 | export default BasicLayout;
133 |
--------------------------------------------------------------------------------
/web/src/pages/system/EmailSetting.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Button, Form, Input, Radio, message, Popover} from 'antd';
3 | import {connect} from "dva";
4 | import styles from './index.module.css';
5 | import lds from 'lodash';
6 |
7 | class EmailSetting extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | // this.setting = JSON.parse(lds.get(store.settings, 'mail_service.value', "{}"));
11 | this.setting = JSON.parse(lds.get(this.props.settings, 'mail_service.Value', "{}"));
12 | this.state = {
13 | loading: false,
14 | mode: this.setting['server'] === undefined ? '1' : '2',
15 | spug_key: lds.get(this.props.settings, 'spug_key.value', ""),
16 | mail_test_loading: false,
17 | }
18 | }
19 |
20 | handleEmailTest = () => {
21 | this.props.form.validateFields((error, data) => {
22 | console.log
23 | if (!error) {
24 | this.setState({mail_test_loading: true});
25 | this.props.dispatch({
26 | type: 'user/settingMailTest',
27 | payload: data,
28 | }).finally(()=> this.setState({mail_test_loading: false}))
29 | }
30 | })
31 | };
32 |
33 | handleSubmit = () => {
34 | const formData = [];
35 | this.props.form.validateFields((err, data) => {
36 | if (!err) {
37 | this.setState({loading: true});
38 | formData.push({name: 'mail_service', value: JSON.stringify(data)});
39 | this.props.dispatch({
40 | type: 'user/settingModify',
41 | payload: {
42 | Data: formData,
43 | }
44 | }).finally(() => this.setState({loading: false}))
45 | }
46 | })
47 | };
48 |
49 | render() {
50 | const {getFieldDecorator} = this.props.form;
51 | const {loading} = this.state;
52 | return (
53 |
54 | 邮件服务设置
55 |
57 |
58 | {getFieldDecorator('server', {
59 | initialValue: this.setting['server'], rules: [
60 | {required: true, message: '请输入邮件服务器地址'}
61 | ]
62 | })(
63 |
64 | )}
65 |
66 |
67 | {getFieldDecorator('port', {
68 | initialValue: this.setting['port'], rules: [
69 | {required: true, message: '请输入邮件服务端口'}
70 | ]
71 | })(
72 |
73 | )}
74 |
75 |
76 | {getFieldDecorator('username', {
77 | initialValue: this.setting['username'], rules: [
78 | {required: true, message: '请输入邮箱账号'}
79 | ]
80 | })(
81 |
82 | )}
83 |
84 |
85 | {getFieldDecorator('password', {
86 | initialValue: this.setting['password'], rules: [
87 | {required: true, message: '请输入邮箱账号对应的密码或授权码'}
88 | ]
89 | })(
90 |
91 | )}
92 |
93 |
94 | {getFieldDecorator('nickname', {initialValue: this.setting['nickname']})(
95 |
96 | )}
97 |
98 |
99 |
100 |
103 |
106 |
107 |
108 |
109 | )
110 | }
111 | }
112 |
113 | export default Form.create()(EmailSetting)
114 |
--------------------------------------------------------------------------------