增加从环境变量获取配置&修复修改密码失败的bug&增加退出登录&修复新增用户无法登录的bug

This commit is contained in:
dushixiang
2020-12-26 23:56:02 +08:00
parent 712e2cfe84
commit 31e18d0418
24 changed files with 195 additions and 53 deletions

View File

@ -8,22 +8,23 @@ WORKDIR /app
COPY . .
RUN go env && CGO_ENABLED=0 go build -o next-terminal main.go
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
RUN apk add gcc g++
RUN go env && CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -a -ldflags '-linkmode external -extldflags "-static"' -o next-terminal main.go
FROM guacamole/guacd:1.2.0
LABEL MAINTAINER="helloworld1024@foxmail.com"
RUN sed -i 's/deb.debian.org/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN apt-get update && apt-get -y install supervisor
RUN mkdir -p /var/log/supervisor
COPY --from=builder /app/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
ENV MYSQL_HOSTNAME 127.0.0.1
ENV MYSQL_PORT 3306
ENV MYSQL_USERNAME mysql
ENV MYSQL_PASSWORD mysql
ENV MYSQL_DATABASE next_terminal
ENV DB sqlite
ENV SQLITE_FILE 'next-terminal.db'
ENV SERVER_PORT 8088
ENV SERVER_ADDR 0.0.0.0:$SERVER_PORT
WORKDIR /usr/local/next-terminal

View File

@ -1,8 +1,11 @@
db: mysql
mysql:
hostname: 127.0.0.1
hostname: 172.16.101.32
port: 3306
username: root
password: root
database: next_terminal
password: mysql
database: next-terminal
sqlite:
file: 'next-terminal.db'
server:
addr: 0.0.0.0:8088

2
go.mod
View File

@ -9,10 +9,10 @@ require (
github.com/labstack/gommon v0.3.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/sftp v1.12.0
github.com/sirupsen/logrus v1.2.0
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.7.1
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
gorm.io/driver/mysql v1.0.3
gorm.io/driver/sqlite v1.1.4 // indirect
gorm.io/gorm v1.20.7
)

4
go.sum
View File

@ -135,6 +135,8 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@ -355,6 +357,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.0.3 h1:+JKBYPfn1tygR1/of/Fh2T8iwuVwzt+PEJmKaXzMQXg=
gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.7 h1:rMS4CL3pNmYq1V5/X+nHHjh1Dx6dnf27+Cai5zabo+M=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=

33
main.go
View File

@ -5,6 +5,7 @@ import (
"github.com/labstack/gommon/log"
"github.com/patrickmn/go-cache"
"gorm.io/driver/mysql"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"next-terminal/pkg/api"
@ -22,19 +23,27 @@ func main() {
}
func Run() error {
global.Config = config.SetupConfig()
var err error
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
global.Config.Mysql.Username,
global.Config.Mysql.Password,
global.Config.Mysql.Hostname,
global.Config.Mysql.Port,
global.Config.Mysql.Database,
)
global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
global.Config, err = config.SetupConfig()
if err != nil {
return err
}
if global.Config.DB == "mysql" {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
global.Config.Mysql.Username,
global.Config.Mysql.Password,
global.Config.Mysql.Hostname,
global.Config.Mysql.Port,
global.Config.Mysql.Database,
)
global.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
} else {
global.DB, err = gorm.Open(sqlite.Open(global.Config.Sqlite.File), &gorm.Config{})
}
if err != nil {
return err
}

View File

@ -13,6 +13,11 @@ type LoginAccount struct {
Password string `json:"password"`
}
type ChangePassword struct {
NewPassword string `json:"newPassword"`
OldPassword string `json:"oldPassword"`
}
func LoginEndpoint(c echo.Context) error {
var loginAccount LoginAccount
if err := c.Bind(&loginAccount); err != nil {
@ -21,10 +26,10 @@ func LoginEndpoint(c echo.Context) error {
user, err := model.FindUserByUsername(loginAccount.Username)
if err != nil {
return err
return Fail(c, -1, "您输入的账号或密码不正确")
}
if err := utils.Encoder.Match([]byte(user.Password), []byte(loginAccount.Password)); err != nil {
return err
return Fail(c, -1, "您输入的账号或密码不正确")
}
token := utils.UUID()
@ -43,7 +48,28 @@ func LogoutEndpoint(c echo.Context) error {
}
func ChangePasswordEndpoint(c echo.Context) error {
return nil
account, _ := GetCurrentAccount(c)
var changePassword ChangePassword
if err := c.Bind(&changePassword); err != nil {
return err
}
if err := utils.Encoder.Match([]byte(account.Password), []byte(changePassword.OldPassword)); err != nil {
return Fail(c, -1, "您输入的原密码不正确")
}
passwd, err := utils.Encoder.Encode([]byte(changePassword.NewPassword))
if err != nil {
return err
}
u := &model.User{
Password: string(passwd),
}
model.UpdateUserById(u, account.ID)
return LogoutEndpoint(c)
}
func InfoEndpoint(c echo.Context) error {

View File

@ -7,6 +7,16 @@ import (
"time"
)
func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if err := next(c); err != nil {
return Fail(c, 0, err.Error())
}
return nil
}
}
func Auth(next echo.HandlerFunc) echo.HandlerFunc {
urls := []string{"download", "recording", "login", "static", "favicon", "logo"}

View File

@ -36,6 +36,7 @@ func SetupRoutes() *echo.Echo {
AllowOrigins: []string{"*"},
AllowMethods: []string{http.MethodGet, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodPost, http.MethodDelete},
}))
e.Use(ErrorHandler)
e.Use(Auth)
e.POST("/login", LoginEndpoint)

View File

@ -16,7 +16,7 @@ func UserCreateEndpoint(c echo.Context) error {
var pass []byte
var err error
if pass, err = utils.Encoder.Encode([]byte("admin")); err != nil {
if pass, err = utils.Encoder.Encode([]byte(item.Password)); err != nil {
return err
}
item.Password = string(pass)

View File

@ -1,16 +1,16 @@
package config
import (
"log"
"strings"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
type Config struct {
DB string
Server *Server
Mysql *Mysql
Sqlite *Sqlite
}
type Mysql struct {
@ -21,36 +21,31 @@ type Mysql struct {
Database string
}
type Sqlite struct {
File string
}
type Server struct {
Addr string
}
func SetupConfig() *Config {
func SetupConfig() (*Config, error) {
viper.SetConfigName("config")
viper.SetConfigType("yaml")
viper.SetConfigType("yml")
viper.AddConfigPath("/etc/next-terminal/")
viper.AddConfigPath("$HOME/.next-terminal")
viper.AddConfigPath(".")
viper.AutomaticEnv()
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
//pflag.String("mysql.hostname", "127.0.0.1", "mysql hostname")
//pflag.Int("mysql.port", 3306, "mysql port")
//pflag.String("mysql.username", "mysql", "mysql username")
//pflag.String("mysql.password", "mysql", "mysql password")
//pflag.String("mysql.database", "next_terminal", "mysql database")
//pflag.String("server.addr", "0.0.0.0:8088", "server listen addr")
pflag.Parse()
_ = viper.BindPFlags(pflag.CommandLine)
err := viper.ReadInConfig()
if err != nil {
log.Fatal(err)
return nil, err
}
var config = &Config{
DB: viper.GetString("db"),
Mysql: &Mysql{
Hostname: viper.GetString("mysql.hostname"),
Port: viper.GetInt("mysql.port"),
@ -58,10 +53,13 @@ func SetupConfig() *Config {
Password: viper.GetString("mysql.password"),
Database: viper.GetString("mysql.database"),
},
Sqlite: &Sqlite{
File: viper.GetString("sqlite.file"),
},
Server: &Server{
Addr: viper.GetString("server.addr"),
},
}
return config
return config, nil
}

View File

@ -5,4 +5,4 @@ command=/usr/local/guacamole/sbin/guacd -b 0.0.0.0 -L info -f
[program:next-terminal]
; command=/usr/local/next-terminal/next-terminal --mysql.hostname %(ENV_MYSQL_HOSTNAME)s --mysql.port %(ENV_MYSQL_PORT)s --mysql.username %(ENV_MYSQL_USERNAME)s --mysql.password %(ENV_MYSQL_PASSWORD)s --mysql.database %(ENV_MYSQL_DATABASE)s --server.addr 0.0.0.0:%(ENV_SERVER_PORT)s
command=/usr/local/next-terminal/next-terminal
command=/usr/local/next-terminal/next-terminal --server.addr 0.0.0.0:%(ENV_SERVER_PORT)s

View File

@ -1,12 +1,12 @@
// prod
export const server = '';
export const wsServer = '';
export const prefix = '';
// export const server = '';
// export const wsServer = '';
// export const prefix = '';
// dev
// export const server = '//127.0.0.1:8088';
// export const wsServer = 'ws://127.0.0.1:8088';
// export const prefix = '';
export const server = '//127.0.0.1:8088';
export const wsServer = 'ws://127.0.0.1:8088';
export const prefix = '';
// export const server = '//172.16.101.32:8080';
// export const wsServer = 'ws://172.16.101.32:8080';

View File

@ -12,9 +12,20 @@ const {Title} = Typography;
class LoginForm extends Component {
state = {
inLogin: false
inLogin: false,
height: window.innerHeight,
width: window.innerWidth
};
componentDidMount() {
window.addEventListener('resize', () => {
this.setState({
height: window.innerHeight,
width: window.innerWidth
})
});
}
handleSubmit = async params => {
this.setState({
inLogin: true
@ -50,7 +61,8 @@ class LoginForm extends Component {
render() {
return (
<div className='login-bg' style={{width: window.innerWidth, height: window.innerHeight, backgroundColor: '#F0F2F5'}}>
<div className='login-bg'
style={{width: this.state.width, height: this.state.height, backgroundColor: '#F0F2F5'}}>
<Card className='login-card' title={null}>
<div style={{textAlign: "center", margin: '15px auto 30px auto', color: '#1890ff'}}>
<Title level={1}>Next Terminal</Title>

View File

@ -36,6 +36,7 @@ import {
} from '@ant-design/icons';
import {itemRender} from "../../utils/utils";
import {PROTOCOL_COLORS} from "../../common/constants";
import Logout from "../user/Logout";
const confirm = Modal.confirm;
const {Search} = Input;
@ -384,6 +385,9 @@ class Asset extends Component {
routes: routes,
itemRender: itemRender
}}
extra={[
<Logout/>
]}
subTitle="资产"
>
</PageHeader>

View File

@ -2,6 +2,7 @@ import React, {Component} from 'react';
import {List, Card, Input, PageHeader} from "antd";
import Console from "../access/Console";
import {itemRender} from "../../utils/utils";
import Logout from "../user/Logout";
const {Search} = Input;
const routes = [
{
@ -55,6 +56,9 @@ class BatchCommand extends Component {
routes: routes,
itemRender: itemRender
}}
extra={[
<Logout/>
]}
subTitle="动态指令"
>
</PageHeader>

View File

@ -30,6 +30,7 @@ import {
UndoOutlined
} from '@ant-design/icons';
import {itemRender} from "../../utils/utils";
import Logout from "../user/Logout";
const confirm = Modal.confirm;
const {Content} = Layout;
@ -359,6 +360,9 @@ class DynamicCommand extends Component {
routes: routes,
itemRender: itemRender
}}
extra={[
<Logout/>
]}
subTitle="批量动态指令执行"
>
</PageHeader>

View File

@ -14,6 +14,7 @@ import {
UndoOutlined
} from '@ant-design/icons';
import {itemRender} from "../../utils/utils";
import Logout from "../user/Logout";
const confirm = Modal.confirm;
const {Search} = Input;
@ -280,6 +281,9 @@ class Credential extends Component {
routes: routes,
itemRender: itemRender
}}
extra={[
<Logout/>
]}
subTitle="访问资产的账户、密钥等"
>
</PageHeader>

View File

@ -1,11 +1,12 @@
import React, {Component} from 'react';
import {Card, Col, PageHeader, Radio, Row, Statistic} from "antd";
import {Button, Card, Col, PageHeader, Radio, Row, Statistic} from "antd";
import {DesktopOutlined, IdcardOutlined, LinkOutlined, UserOutlined} from '@ant-design/icons';
import {itemRender} from '../../utils/utils'
import request from "../../common/request";
import './Dashboard.css'
import {Link} from "react-router-dom";
import {Area} from '@ant-design/charts';
import Logout from "../user/Logout";
const routes = [
@ -86,6 +87,9 @@ class Dashboard extends Component {
itemRender: itemRender
}}
subTitle="仪表盘"
extra={[
<Logout/>
]}
>
</PageHeader>

View File

@ -31,6 +31,7 @@ import {
UndoOutlined
} from "@ant-design/icons";
import {PROTOCOL_COLORS} from "../../common/constants";
import Logout from "../user/Logout";
const confirm = Modal.confirm;
const {Content} = Layout;
@ -357,6 +358,9 @@ class OfflineSession extends Component {
routes: routes,
itemRender: itemRender
}}
extra={[
<Logout/>
]}
subTitle="离线会话管理"
>
</PageHeader>

View File

@ -31,6 +31,7 @@ import {
UndoOutlined
} from "@ant-design/icons";
import Monitor from "../access/Monitor";
import Logout from "../user/Logout";
const confirm = Modal.confirm;
const {Content} = Layout;
@ -348,6 +349,9 @@ class OnlineSession extends Component {
routes: routes,
itemRender: itemRender
}}
extra={[
<Logout/>
]}
subTitle="查询实时在线会话"
>
</PageHeader>

View File

@ -3,6 +3,7 @@ import {Button, Form, Input, Layout, PageHeader, Select, Switch, Tabs, Typograph
import {itemRender} from '../../utils/utils'
import request from "../../common/request";
import {message} from "antd/es";
import Logout from "../user/Logout";
const {Content} = Layout;
const {Option} = Select;
@ -107,6 +108,9 @@ class Setting extends Component {
routes: routes,
itemRender: itemRender
}}
extra={[
<Logout/>
]}
subTitle="系统设置"
>
</PageHeader>

View File

@ -3,6 +3,7 @@ import {Button, Form, Input, Layout, PageHeader} from "antd";
import {itemRender} from '../../utils/utils'
import request from "../../common/request";
import {message} from "antd/es";
import Logout from "./Logout";
const {Content} = Layout;
@ -61,7 +62,8 @@ class Info extends Component {
changePassword = async (values) => {
let result = await request.post('/change-password', values);
if (result.code === 1) {
message.success('密码修改成功');
message.success('密码修改成功,即将跳转至登录页面');
window.location.href = '/#';
} else {
message.error(result.message);
}
@ -77,6 +79,9 @@ class Info extends Component {
routes: routes,
itemRender: itemRender
}}
extra={[
<Logout/>
]}
subTitle="个人中心"
>
</PageHeader>

View File

@ -0,0 +1,37 @@
import React, {Component} from 'react';
import {Button, message, Popconfirm} from "antd";
import request from "../../common/request";
class Logout extends Component {
confirm = async (e) => {
let result = await request.post('/logout');
if (result['code'] !== 1) {
message.error(result['message']);
} else {
message.success('退出登录成功,即将跳转至登录页面。');
window.location.reload();
}
}
render() {
return (
<div>
<Popconfirm
title="您确定要退出登录吗?"
onConfirm={this.confirm}
okText="确定"
cancelText="取消"
placement="left"
>
<Button key="login-btn" type="dashed">
退出登录
</Button>,
</Popconfirm>
</div>
);
}
}
export default Logout;

View File

@ -28,6 +28,7 @@ import {
SyncOutlined,
UndoOutlined
} from '@ant-design/icons';
import Logout from "./Logout";
const confirm = Modal.confirm;
const {Search} = Input;
@ -308,6 +309,9 @@ class User extends Component {
routes: routes,
itemRender: itemRender
}}
extra={[
<Logout/>
]}
subTitle="平台用户管理"
>
</PageHeader>