From 31e18d04186a2396d437193ec30d84e1a1220cbd Mon Sep 17 00:00:00 2001 From: dushixiang Date: Sat, 26 Dec 2020 23:56:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BB=8E=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E5=8F=98=E9=87=8F=E8=8E=B7=E5=8F=96=E9=85=8D=E7=BD=AE&?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BF=AE=E6=94=B9=E5=AF=86=E7=A0=81=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5=E7=9A=84bug&=E5=A2=9E=E5=8A=A0=E9=80=80=E5=87=BA?= =?UTF-8?q?=E7=99=BB=E5=BD=95&=E4=BF=AE=E5=A4=8D=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E6=97=A0=E6=B3=95=E7=99=BB=E5=BD=95=E7=9A=84?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 13 +++---- config.yml | 9 +++-- go.mod | 2 +- go.sum | 4 +++ main.go | 33 ++++++++++------- pkg/api/account.go | 32 +++++++++++++++-- pkg/api/middleware.go | 10 ++++++ pkg/api/routes.go | 1 + pkg/api/user.go | 2 +- pkg/config/config.go | 30 ++++++++-------- supervisord.conf | 2 +- web/src/common/constants.js | 12 +++---- web/src/components/Login.js | 16 +++++++-- web/src/components/asset/Asset.js | 4 +++ web/src/components/command/BatchCommand.js | 4 +++ web/src/components/command/DynamicCommand.js | 4 +++ web/src/components/credential/Credential.js | 4 +++ web/src/components/dashboard/Dashboard.js | 6 +++- web/src/components/session/OfflineSession.js | 4 +++ web/src/components/session/OnlineSession.js | 4 +++ web/src/components/setting/Setting.js | 4 +++ web/src/components/user/Info.js | 7 +++- web/src/components/user/Logout.js | 37 ++++++++++++++++++++ web/src/components/user/User.js | 4 +++ 24 files changed, 195 insertions(+), 53 deletions(-) create mode 100644 web/src/components/user/Logout.js diff --git a/Dockerfile b/Dockerfile index d2029a8..d76895a 100644 --- a/Dockerfile +++ b/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 diff --git a/config.yml b/config.yml index 097253e..62aa885 100644 --- a/config.yml +++ b/config.yml @@ -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 \ No newline at end of file diff --git a/go.mod b/go.mod index d8d3ab3..4ef47a9 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 7e3cf5b..c376c8a 100644 --- a/go.sum +++ b/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= diff --git a/main.go b/main.go index aabada3..90287c2 100644 --- a/main.go +++ b/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 } diff --git a/pkg/api/account.go b/pkg/api/account.go index 73d914a..ed14a1f 100644 --- a/pkg/api/account.go +++ b/pkg/api/account.go @@ -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 { diff --git a/pkg/api/middleware.go b/pkg/api/middleware.go index 9145872..748b9ab 100644 --- a/pkg/api/middleware.go +++ b/pkg/api/middleware.go @@ -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"} diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 71e907c..6f9dec2 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -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) diff --git a/pkg/api/user.go b/pkg/api/user.go index 40cec03..a11d8eb 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -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) diff --git a/pkg/config/config.go b/pkg/config/config.go index c2b7ae4..701c664 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -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 } diff --git a/supervisord.conf b/supervisord.conf index eb5981e..b6099d3 100644 --- a/supervisord.conf +++ b/supervisord.conf @@ -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 \ No newline at end of file +command=/usr/local/next-terminal/next-terminal --server.addr 0.0.0.0:%(ENV_SERVER_PORT)s \ No newline at end of file diff --git a/web/src/common/constants.js b/web/src/common/constants.js index b81685f..bdea679 100644 --- a/web/src/common/constants.js +++ b/web/src/common/constants.js @@ -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'; diff --git a/web/src/components/Login.js b/web/src/components/Login.js index a98fb31..3da91d0 100644 --- a/web/src/components/Login.js +++ b/web/src/components/Login.js @@ -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 ( -
+
Next Terminal diff --git a/web/src/components/asset/Asset.js b/web/src/components/asset/Asset.js index fbdbbd7..bcfc889 100644 --- a/web/src/components/asset/Asset.js +++ b/web/src/components/asset/Asset.js @@ -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={[ + + ]} subTitle="资产" > diff --git a/web/src/components/command/BatchCommand.js b/web/src/components/command/BatchCommand.js index d4df97d..07c49e5 100644 --- a/web/src/components/command/BatchCommand.js +++ b/web/src/components/command/BatchCommand.js @@ -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={[ + + ]} subTitle="动态指令" > diff --git a/web/src/components/command/DynamicCommand.js b/web/src/components/command/DynamicCommand.js index 7ea5d67..605042e 100644 --- a/web/src/components/command/DynamicCommand.js +++ b/web/src/components/command/DynamicCommand.js @@ -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={[ + + ]} subTitle="批量动态指令执行" > diff --git a/web/src/components/credential/Credential.js b/web/src/components/credential/Credential.js index ec709dc..e75c96d 100644 --- a/web/src/components/credential/Credential.js +++ b/web/src/components/credential/Credential.js @@ -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={[ + + ]} subTitle="访问资产的账户、密钥等" > diff --git a/web/src/components/dashboard/Dashboard.js b/web/src/components/dashboard/Dashboard.js index 43fdaf1..f7d9633 100644 --- a/web/src/components/dashboard/Dashboard.js +++ b/web/src/components/dashboard/Dashboard.js @@ -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={[ + + ]} > diff --git a/web/src/components/session/OfflineSession.js b/web/src/components/session/OfflineSession.js index 3933ef9..404d5b2 100644 --- a/web/src/components/session/OfflineSession.js +++ b/web/src/components/session/OfflineSession.js @@ -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={[ + + ]} subTitle="离线会话管理" > diff --git a/web/src/components/session/OnlineSession.js b/web/src/components/session/OnlineSession.js index c323d4e..faa387a 100644 --- a/web/src/components/session/OnlineSession.js +++ b/web/src/components/session/OnlineSession.js @@ -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={[ + + ]} subTitle="查询实时在线会话" > diff --git a/web/src/components/setting/Setting.js b/web/src/components/setting/Setting.js index c00afee..c32c807 100644 --- a/web/src/components/setting/Setting.js +++ b/web/src/components/setting/Setting.js @@ -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={[ + + ]} subTitle="系统设置" > diff --git a/web/src/components/user/Info.js b/web/src/components/user/Info.js index 199dda3..039f929 100644 --- a/web/src/components/user/Info.js +++ b/web/src/components/user/Info.js @@ -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={[ + + ]} subTitle="个人中心" > diff --git a/web/src/components/user/Logout.js b/web/src/components/user/Logout.js new file mode 100644 index 0000000..a8ec264 --- /dev/null +++ b/web/src/components/user/Logout.js @@ -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 ( +
+ + , + + +
+ ); + } +} + +export default Logout; \ No newline at end of file diff --git a/web/src/components/user/User.js b/web/src/components/user/User.js index f634cb9..72aa443 100644 --- a/web/src/components/user/User.js +++ b/web/src/components/user/User.js @@ -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={[ + + ]} subTitle="平台用户管理" >