增加从环境变量获取配置&修复修改密码失败的bug&增加退出登录&修复新增用户无法登录的bug
This commit is contained in:
13
Dockerfile
13
Dockerfile
@ -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
|
||||
|
||||
|
@ -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
2
go.mod
@ -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
4
go.sum
@ -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
33
main.go
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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"}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
@ -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';
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
37
web/src/components/user/Logout.js
Normal file
37
web/src/components/user/Logout.js
Normal 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;
|
@ -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>
|
||||
|
Reference in New Issue
Block a user