修复 「1.2.2 用户管理-用户列表勾选单一用户会全选 」 close #216
This commit is contained in:
@ -11,7 +11,7 @@
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 24px;
|
||||
margin: 30px 17px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@ -128,15 +128,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.km-header-logo {
|
||||
font-size: 18px;
|
||||
/*font-weight: 500;*/
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
/*padding: 0 16px;*/
|
||||
}
|
||||
|
||||
.km-header-right {
|
||||
@ -145,12 +137,6 @@
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.km-header-right {
|
||||
text-align: right;
|
||||
height: 100%;
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.km-header-right-item {
|
||||
cursor: pointer;
|
||||
/*padding: 23px 12px;*/
|
||||
|
@ -12,6 +12,8 @@ import OfflineSession from "./components/session/OfflineSession";
|
||||
import Login from "./components/Login";
|
||||
import DynamicCommand from "./components/command/DynamicCommand";
|
||||
import Credential from "./components/credential/Credential";
|
||||
import LogoWithName from './images/logo-with-name.svg'
|
||||
import Logo from './images/logo.svg'
|
||||
import {
|
||||
ApiOutlined,
|
||||
AuditOutlined,
|
||||
@ -23,7 +25,8 @@ import {
|
||||
DesktopOutlined,
|
||||
DisconnectOutlined,
|
||||
DownOutlined,
|
||||
FolderOutlined, GithubOutlined,
|
||||
FolderOutlined,
|
||||
GithubOutlined,
|
||||
HddOutlined,
|
||||
IdcardOutlined,
|
||||
InsuranceOutlined,
|
||||
@ -73,13 +76,26 @@ class App extends Component {
|
||||
'nickname': '未定义'
|
||||
},
|
||||
package: NT_PACKAGE(),
|
||||
triggerMenu: true
|
||||
triggerMenu: true,
|
||||
logo: LogoWithName,
|
||||
logoWidth: 140
|
||||
};
|
||||
|
||||
onCollapse = () => {
|
||||
this.setState({
|
||||
collapsed: !this.state.collapsed,
|
||||
});
|
||||
let collapsed = !this.state.collapsed;
|
||||
if (collapsed) {
|
||||
this.setState({
|
||||
logo: Logo,
|
||||
logoWidth: 46,
|
||||
collapsed: collapsed,
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
logo: LogoWithName,
|
||||
logoWidth: 140,
|
||||
collapsed: collapsed,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
@ -94,7 +110,7 @@ class App extends Component {
|
||||
|
||||
async getInfo() {
|
||||
|
||||
let result = await request.get('/info');
|
||||
let result = await request.get('/account/info');
|
||||
if (result['code'] === 1) {
|
||||
sessionStorage.setItem('user', JSON.stringify(result['data']));
|
||||
this.setState({
|
||||
@ -126,8 +142,8 @@ class App extends Component {
|
||||
sessionStorage.setItem('openKeys', JSON.stringify(openKeys));
|
||||
}
|
||||
|
||||
confirm = async (e) => {
|
||||
let result = await request.post('/logout');
|
||||
confirm = async () => {
|
||||
let result = await request.post('/account/logout');
|
||||
if (result['code'] !== 1) {
|
||||
message.error(result['message']);
|
||||
} else {
|
||||
@ -180,14 +196,7 @@ class App extends Component {
|
||||
<>
|
||||
<Sider collapsible collapsed={this.state.collapsed} trigger={null}>
|
||||
<div className="logo">
|
||||
<img
|
||||
src='data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjxwYXRoIGQ9Ik0yNzIgMTIyLjI0aDQ4MHYxNTcuMDU2aDk2Vi40NDhoLTk2TDI3MiAwYy01Mi44IDAtOTYgLjQ0OC05NiAuNDQ4djI3OC44NDhoOTZ2LTE1Ny4xMnptNDAzLjY0OCA2MDMuMzkyTDg5NiA1MTIgNjc1LjY0OCAyOTguMzY4IDYwOCAzNjQuNDggNzYwLjEyOCA1MTIgNjA4IDY1OS41Mmw2Ny42NDggNjYuMTEyek00MTYgNjU5LjUyTDI2My44MDggNTEyIDQxNiAzNjQuNDhsLTY3LjcxMi02Ni4xMTJMMTI4IDUxMmwyMjAuMjg4IDIxMy42MzJMNDE2IDY1OS41MnptMzM2IDI0Mi4zMDRIMjcydi0xNTcuMTJoLTk2VjEwMjRoNjcyVjc0NC43MDRoLTk2djE1Ny4xMnoiIGZpbGw9IiNmZmYiLz48L3N2Zz4='
|
||||
alt='logo'/>
|
||||
{
|
||||
!this.state.collapsed ?
|
||||
<> <h1>Next Terminal</h1></> :
|
||||
null
|
||||
}
|
||||
<img src={this.state.logo} alt='logo' width={this.state.logoWidth}/>
|
||||
</div>
|
||||
|
||||
<Menu
|
||||
@ -314,8 +323,10 @@ class App extends Component {
|
||||
|
||||
<div className='layout-header-right'>
|
||||
<div className={'layout-header-right-item'}>
|
||||
<a style={{color: 'black'}} target='_blank' href='https://github.com/dushixiang/next-terminal' rel='noreferrer noopener'>
|
||||
<GithubOutlined />
|
||||
<a style={{color: 'black'}} target='_blank'
|
||||
href='https://github.com/dushixiang/next-terminal'
|
||||
rel='noreferrer noopener'>
|
||||
<GithubOutlined/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -350,7 +361,8 @@ class App extends Component {
|
||||
<Route path="/strategy" component={Strategy}/>
|
||||
|
||||
<Footer style={{textAlign: 'center'}}>
|
||||
Next Terminal ©2021 dushixiang Version:{this.state.package['version']}
|
||||
Copyright © 2020-2022 dushixiang, All Rights Reserved.
|
||||
Version:{this.state.package['version']}
|
||||
</Footer>
|
||||
</Layout>
|
||||
</> :
|
||||
@ -359,11 +371,7 @@ class App extends Component {
|
||||
<div className='km-header'>
|
||||
<div style={{flex: '1 1 0%'}}>
|
||||
<Link to={'/'}>
|
||||
<img
|
||||
style={{paddingBottom: 4, marginRight: 5}}
|
||||
src='data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjxwYXRoIGQ9Ik0yNzIgMTIyLjI0aDQ4MHYxNTcuMDU2aDk2Vi40NDhoLTk2TDI3MiAwYy01Mi44IDAtOTYgLjQ0OC05NiAuNDQ4djI3OC44NDhoOTZ2LTE1Ny4xMnptNDAzLjY0OCA2MDMuMzkyTDg5NiA1MTIgNjc1LjY0OCAyOTguMzY4IDYwOCAzNjQuNDggNzYwLjEyOCA1MTIgNjA4IDY1OS41Mmw2Ny42NDggNjYuMTEyek00MTYgNjU5LjUyTDI2My44MDggNTEyIDQxNiAzNjQuNDhsLTY3LjcxMi02Ni4xMTJMMTI4IDUxMmwyMjAuMjg4IDIxMy42MzJMNDE2IDY1OS41MnptMzM2IDI0Mi4zMDRIMjcydi0xNTcuMTJoLTk2VjEwMjRoNjcyVjc0NC43MDRoLTk2djE1Ny4xMnoiIGZpbGw9IiNmZmYiLz48L3N2Zz4='
|
||||
alt='logo'/>
|
||||
<span className='km-header-logo'>Next Terminal</span>
|
||||
<img src={this.state.logo} alt='logo' width={120}/>
|
||||
</Link>
|
||||
|
||||
<Link to={'/my-file'}>
|
||||
@ -400,7 +408,8 @@ class App extends Component {
|
||||
</Layout>
|
||||
</Content>
|
||||
<Footer style={{textAlign: 'center'}}>
|
||||
Next Terminal ©2021 dushixiang Version:{this.state.package['version']}
|
||||
Copyright © 2020-2022 dushixiang, All Rights Reserved.
|
||||
Version:{this.state.package['version']}
|
||||
</Footer>
|
||||
</>
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
@ -5,6 +5,9 @@ import request from "../common/request";
|
||||
import {message} from "antd/es";
|
||||
import {withRouter} from "react-router-dom";
|
||||
import {LockOutlined, OneToOneOutlined, UserOutlined} from '@ant-design/icons';
|
||||
import Particles from "react-tsparticles";
|
||||
import Background from '../images/bg.png'
|
||||
import {setToken} from "../utils/utils";
|
||||
|
||||
const {Title} = Typography;
|
||||
|
||||
@ -56,7 +59,7 @@ class LoginForm extends Component {
|
||||
// 跳转登录
|
||||
sessionStorage.removeItem('current');
|
||||
sessionStorage.removeItem('openKeys');
|
||||
localStorage.setItem('X-Auth-Token', result['data']);
|
||||
setToken(result['data']);
|
||||
// this.props.history.push();
|
||||
window.location.href = "/"
|
||||
} catch (e) {
|
||||
@ -85,7 +88,7 @@ class LoginForm extends Component {
|
||||
// 跳转登录
|
||||
sessionStorage.removeItem('current');
|
||||
sessionStorage.removeItem('openKeys');
|
||||
localStorage.setItem('X-Auth-Token', result['data']);
|
||||
setToken(result['data']);
|
||||
// this.props.history.push();
|
||||
window.location.href = "/"
|
||||
} catch (e) {
|
||||
@ -106,7 +109,90 @@ class LoginForm extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div className='login-bg'
|
||||
style={{width: this.state.width, height: this.state.height, backgroundColor: '#F0F2F5'}}>
|
||||
style={{width: this.state.width, height: this.state.height}}>
|
||||
<Particles
|
||||
id="tsparticles"
|
||||
options={{
|
||||
background: {
|
||||
color: {
|
||||
// value: "#0d47a1",
|
||||
},
|
||||
image: `url(${Background})`,
|
||||
repeat: 'no-repeat',
|
||||
size: '100% 100%'
|
||||
},
|
||||
fpsLimit: 60,
|
||||
interactivity: {
|
||||
events: {
|
||||
onClick: {
|
||||
enable: true,
|
||||
mode: "push",
|
||||
},
|
||||
onHover: {
|
||||
enable: true,
|
||||
mode: "repulse",
|
||||
},
|
||||
resize: true,
|
||||
},
|
||||
modes: {
|
||||
bubble: {
|
||||
distance: 400,
|
||||
duration: 2,
|
||||
opacity: 0.8,
|
||||
size: 40,
|
||||
},
|
||||
push: {
|
||||
quantity: 4,
|
||||
},
|
||||
repulse: {
|
||||
distance: 200,
|
||||
duration: 0.4,
|
||||
},
|
||||
},
|
||||
},
|
||||
particles: {
|
||||
color: {
|
||||
value: "#ffffff",
|
||||
},
|
||||
links: {
|
||||
color: "#ffffff",
|
||||
distance: 150,
|
||||
enable: true,
|
||||
opacity: 0.5,
|
||||
width: 1,
|
||||
},
|
||||
collisions: {
|
||||
enable: true,
|
||||
},
|
||||
move: {
|
||||
direction: "none",
|
||||
enable: true,
|
||||
outMode: "bounce",
|
||||
random: false,
|
||||
speed: 6,
|
||||
straight: false,
|
||||
},
|
||||
number: {
|
||||
density: {
|
||||
enable: true,
|
||||
value_area: 800,
|
||||
},
|
||||
value: 80,
|
||||
},
|
||||
opacity: {
|
||||
value: 0.5,
|
||||
},
|
||||
shape: {
|
||||
type: "circle",
|
||||
},
|
||||
size: {
|
||||
random: true,
|
||||
value: 5,
|
||||
},
|
||||
},
|
||||
detectRetina: true,
|
||||
}}
|
||||
/>
|
||||
<Card className='login-card' title={null}>
|
||||
<div style={{textAlign: "center", margin: '15px auto 30px auto', color: '#1890ff'}}>
|
||||
<Title level={1}>Next Terminal</Title>
|
||||
@ -140,9 +226,6 @@ class LoginForm extends Component {
|
||||
.then(values => {
|
||||
this.handleOk(values);
|
||||
// this.formRef.current.resetFields();
|
||||
})
|
||||
.catch(info => {
|
||||
|
||||
});
|
||||
}}
|
||||
onCancel={this.handleCancel}>
|
||||
|
@ -1,3 +1,3 @@
|
||||
.container div {
|
||||
.container > div {
|
||||
margin: 0 auto;
|
||||
}
|
@ -13,7 +13,7 @@ import {
|
||||
LineChartOutlined,
|
||||
WindowsOutlined
|
||||
} from '@ant-design/icons';
|
||||
import {exitFull, getToken, isEmpty, requestFullScreen} from "../../utils/utils";
|
||||
import {exitFull, getToken, isEmpty, requestFullScreen, setToken} from "../../utils/utils";
|
||||
import './Access.css'
|
||||
import Draggable from 'react-draggable';
|
||||
import FileSystem from "../devops/FileSystem";
|
||||
@ -38,13 +38,14 @@ class Access extends Component {
|
||||
state = {
|
||||
session: {},
|
||||
sessionId: '',
|
||||
client: {},
|
||||
client: undefined,
|
||||
scale: 1,
|
||||
clientState: STATE_IDLE,
|
||||
clipboardVisible: false,
|
||||
clipboardText: '',
|
||||
containerOverflow: 'hidden',
|
||||
containerWidth: 0,
|
||||
containerHeight: 0,
|
||||
containerWidth: 1024,
|
||||
containerHeight: 768,
|
||||
uploadAction: '',
|
||||
uploadHeaders: {},
|
||||
keyboard: {},
|
||||
@ -57,15 +58,39 @@ class Access extends Component {
|
||||
fullScreenBtnText: '进入全屏',
|
||||
sink: undefined,
|
||||
commands: [],
|
||||
showFileSystem: false
|
||||
showFileSystem: false,
|
||||
external: false,
|
||||
fixedSize: false,
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
|
||||
let urlParams = new URLSearchParams(this.props.location.search);
|
||||
let assetId = urlParams.get('assetId');
|
||||
document.title = urlParams.get('assetName');
|
||||
let protocol = urlParams.get('protocol');
|
||||
let width = urlParams.get('width');
|
||||
let height = urlParams.get('height');
|
||||
let fixedSize = false;
|
||||
|
||||
if (width && height) {
|
||||
fixedSize = true
|
||||
} else {
|
||||
width = window.innerWidth;
|
||||
height = window.innerHeight;
|
||||
}
|
||||
|
||||
let shareSessionId = urlParams.get('shareSessionId');
|
||||
let external = false;
|
||||
if (shareSessionId && shareSessionId !== '') {
|
||||
setToken(shareSessionId);
|
||||
external = true;
|
||||
let shareSession = await this.getShareSession(shareSessionId);
|
||||
if (!shareSession) {
|
||||
return
|
||||
}
|
||||
assetId = shareSession['assetId'];
|
||||
}
|
||||
|
||||
let session = await this.createSession(assetId);
|
||||
if (!session) {
|
||||
return;
|
||||
@ -79,10 +104,14 @@ class Access extends Component {
|
||||
session: session,
|
||||
sessionId: sessionId,
|
||||
protocol: protocol,
|
||||
showFileSystem: session['fileSystem'] === '1'
|
||||
showFileSystem: session['fileSystem'] === '1',
|
||||
external: external,
|
||||
fixedSize: fixedSize,
|
||||
containerWidth: width,
|
||||
containerHeight: height,
|
||||
});
|
||||
|
||||
this.renderDisplay(sessionId, protocol);
|
||||
this.renderDisplay(sessionId, protocol, width, height);
|
||||
|
||||
window.addEventListener('resize', this.onWindowResize);
|
||||
window.onfocus = this.onWindowFocus;
|
||||
@ -95,6 +124,10 @@ class Access extends Component {
|
||||
}
|
||||
|
||||
sendClipboard(data) {
|
||||
if (this.state.session['paste'] === '0') {
|
||||
message.warn('禁止粘贴');
|
||||
return
|
||||
}
|
||||
let writer;
|
||||
|
||||
// Create stream with proper mimetype
|
||||
@ -133,6 +166,7 @@ class Access extends Component {
|
||||
}
|
||||
|
||||
onTunnelStateChange = (state) => {
|
||||
console.log(state)
|
||||
if (state === Guacamole.Tunnel.State.CLOSED) {
|
||||
console.log('web socket 已关闭');
|
||||
}
|
||||
@ -175,12 +209,13 @@ class Access extends Component {
|
||||
break;
|
||||
case STATE_CONNECTED:
|
||||
this.onWindowResize(null);
|
||||
Modal.destroyAll();
|
||||
message.destroy();
|
||||
message.success('连接成功');
|
||||
// 向后台发送请求,更新会话的状态
|
||||
this.updateSessionStatus(this.state.sessionId).then(_ => {
|
||||
})
|
||||
if (this.state.protocol === 'ssh') {
|
||||
if (this.state.protocol === 'ssh' && !this.state.external) {
|
||||
// 加载指令
|
||||
this.getCommands();
|
||||
}
|
||||
@ -300,9 +335,11 @@ class Access extends Component {
|
||||
}
|
||||
|
||||
clientClipboardReceived = (stream, mimetype) => {
|
||||
console.log('clientClipboardReceived', mimetype)
|
||||
if (this.state.session['copy'] === '0') {
|
||||
message.warn('禁止复制');
|
||||
return
|
||||
}
|
||||
let reader;
|
||||
|
||||
// If the received data is text, read it as a simple string
|
||||
if (/^text\//.exec(mimetype)) {
|
||||
reader = new Guacamole.StringReader(stream);
|
||||
@ -378,9 +415,18 @@ class Access extends Component {
|
||||
return result['data'];
|
||||
}
|
||||
|
||||
async renderDisplay(sessionId, protocol) {
|
||||
async getShareSession(shareSessionId) {
|
||||
let result = await request.get(`/share-sessions/${shareSessionId}`);
|
||||
if (result['code'] !== 1) {
|
||||
this.showMessage(result['message']);
|
||||
return undefined;
|
||||
}
|
||||
return result['data'];
|
||||
}
|
||||
|
||||
let tunnel = new Guacamole.WebSocketTunnel(wsServer + '/tunnel');
|
||||
async renderDisplay(sessionId, protocol, width, height) {
|
||||
|
||||
let tunnel = new Guacamole.WebSocketTunnel(`${wsServer}/sessions/${sessionId}/tunnel`);
|
||||
|
||||
tunnel.onstatechange = this.onTunnelStateChange;
|
||||
// Get new client instance
|
||||
@ -404,17 +450,16 @@ class Access extends Component {
|
||||
const element = client.getDisplay().getElement();
|
||||
display.appendChild(element);
|
||||
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
let scale = 1;
|
||||
let dpi = 96;
|
||||
if (protocol === 'ssh' || protocol === 'telnet') {
|
||||
dpi = dpi * 2;
|
||||
scale = 0.5;
|
||||
}
|
||||
|
||||
let token = getToken();
|
||||
|
||||
let params = {
|
||||
'sessionId': sessionId,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'dpi': dpi,
|
||||
@ -439,13 +484,9 @@ class Access extends Component {
|
||||
};
|
||||
|
||||
mouse.onmousemove = function (mouseState) {
|
||||
if (protocol === 'ssh' || protocol === 'telnet') {
|
||||
mouseState.x = mouseState.x * 2;
|
||||
mouseState.y = mouseState.y * 2;
|
||||
client.sendMouseState(mouseState);
|
||||
} else {
|
||||
client.sendMouseState(mouseState);
|
||||
}
|
||||
mouseState.x = mouseState.x / scale;
|
||||
mouseState.y = mouseState.y / scale;
|
||||
client.sendMouseState(mouseState);
|
||||
};
|
||||
|
||||
const sink = new Guacamole.InputSink();
|
||||
@ -460,8 +501,7 @@ class Access extends Component {
|
||||
|
||||
this.setState({
|
||||
client: client,
|
||||
containerWidth: width,
|
||||
containerHeight: height,
|
||||
scale: scale,
|
||||
keyboard: keyboard,
|
||||
sink: sink
|
||||
});
|
||||
@ -469,19 +509,14 @@ class Access extends Component {
|
||||
|
||||
onWindowResize = (e) => {
|
||||
|
||||
if (this.state.client) {
|
||||
if (this.state.client && !this.state.fixedSize) {
|
||||
const display = this.state.client.getDisplay();
|
||||
let scale = this.state.scale;
|
||||
display.scale(scale);
|
||||
let width = window.innerWidth;
|
||||
let height = window.innerHeight;
|
||||
|
||||
const width = window.innerWidth;
|
||||
const height = window.innerHeight;
|
||||
|
||||
if (this.state.protocol === 'ssh' || this.state.protocol === 'telnet') {
|
||||
let r = 2;
|
||||
display.scale(1 / r);
|
||||
this.state.client.sendSize(width * r, height * r);
|
||||
} else {
|
||||
this.state.client.sendSize(width, height);
|
||||
}
|
||||
this.state.client.sendSize(width / scale, height / scale);
|
||||
|
||||
this.setState({
|
||||
containerWidth: width,
|
||||
@ -502,47 +537,17 @@ class Access extends Component {
|
||||
|
||||
onWindowFocus = (e) => {
|
||||
if (navigator.clipboard && this.state.clientState === STATE_CONNECTED) {
|
||||
navigator.clipboard.readText().then((text) => {
|
||||
this.sendClipboard({
|
||||
'data': text,
|
||||
'type': 'text/plain'
|
||||
});
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
onPaste = (e) => {
|
||||
const cbd = e.clipboardData;
|
||||
const ua = window.navigator.userAgent;
|
||||
|
||||
// 如果是 Safari 直接 return
|
||||
if (!(e.clipboardData && e.clipboardData.items)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mac平台下Chrome49版本以下 复制Finder中的文件的Bug Hack掉
|
||||
if (cbd.items && cbd.items.length === 2 && cbd.items[0].kind === "string" && cbd.items[1].kind === "file" &&
|
||||
cbd.types && cbd.types.length === 2 && cbd.types[0] === "text/plain" && cbd.types[1] === "Files" &&
|
||||
ua.match(/Macintosh/i) && Number(ua.match(/Chrome\/(\d{2})/i)[1]) < 49) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < cbd.items.length; i++) {
|
||||
let item = cbd.items[i];
|
||||
if (item.kind === "file") {
|
||||
let blob = item.getAsFile();
|
||||
if (blob.size === 0) {
|
||||
return;
|
||||
}
|
||||
// blob 就是从剪切板获得的文件 可以进行上传或其他操作
|
||||
} else if (item.kind === 'string') {
|
||||
item.getAsString((str) => {
|
||||
try {
|
||||
navigator.clipboard.readText().then((text) => {
|
||||
this.sendClipboard({
|
||||
'data': str,
|
||||
'data': text,
|
||||
'type': 'text/plain'
|
||||
});
|
||||
})
|
||||
} catch (e) {
|
||||
// console.error(e);
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -616,7 +621,8 @@ class Access extends Component {
|
||||
<div className="container" style={{
|
||||
overflow: this.state.containerOverflow,
|
||||
width: this.state.containerWidth,
|
||||
height: this.state.containerHeight
|
||||
height: this.state.containerHeight,
|
||||
margin: '0 auto'
|
||||
}}>
|
||||
<div id="display"/>
|
||||
</div>
|
||||
@ -630,16 +636,20 @@ class Access extends Component {
|
||||
</Affix>
|
||||
</Draggable>
|
||||
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 50, right: 100}}>
|
||||
<Button icon={<CopyOutlined/>} disabled={this.state.clientState !== STATE_CONNECTED}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
clipboardVisible: true
|
||||
});
|
||||
}}/>
|
||||
</Affix>
|
||||
</Draggable>
|
||||
{
|
||||
this.state.session['copy'] === '1' || this.state.session['paste'] === '1' ?
|
||||
<Draggable>
|
||||
<Affix style={{position: 'absolute', top: 50, right: 100}}>
|
||||
<Button icon={<CopyOutlined/>} disabled={this.state.clientState !== STATE_CONNECTED}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
clipboardVisible: true
|
||||
});
|
||||
}}/>
|
||||
</Affix>
|
||||
</Draggable> : undefined
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
this.state.protocol === 'vnc' ?
|
||||
@ -734,7 +744,6 @@ class Access extends Component {
|
||||
placement="right"
|
||||
width={window.innerWidth * 0.8}
|
||||
closable={true}
|
||||
// maskClosable={false}
|
||||
onClose={() => {
|
||||
this.focus();
|
||||
this.setState({
|
||||
|
@ -53,8 +53,8 @@ class Term extends Component {
|
||||
},
|
||||
rightClickSelectsWord: true,
|
||||
});
|
||||
|
||||
term.open(document.getElementById('terminal'));
|
||||
let elementTerm = document.getElementById('terminal');
|
||||
term.open(elementTerm);
|
||||
const fitAddon = new FitAddon();
|
||||
term.loadAddon(fitAddon);
|
||||
fitAddon.fit();
|
||||
@ -64,7 +64,6 @@ class Term extends Component {
|
||||
|
||||
term.onSelectionChange(async () => {
|
||||
let selection = term.getSelection();
|
||||
console.log(`selection: [${selection}]`);
|
||||
this.setState({
|
||||
selection: selection
|
||||
})
|
||||
@ -80,6 +79,29 @@ class Term extends Component {
|
||||
return !(e.ctrlKey && e.key === 'v');
|
||||
});
|
||||
|
||||
document.body.oncopy = (event) => {
|
||||
event.preventDefault();
|
||||
if (this.state.session['copy'] === '0') {
|
||||
message.warn('禁止复制')
|
||||
if (event.clipboardData) {
|
||||
return event.clipboardData.setData('text', '');
|
||||
} else {
|
||||
// 兼容IE
|
||||
return window.clipboardData.setData("text", '');
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
document.body.onpaste = (event) => {
|
||||
event.preventDefault();
|
||||
if (this.state.session['paste'] === '0') {
|
||||
message.warn('禁止粘贴')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
term.onData(data => {
|
||||
let webSocket = this.state.webSocket;
|
||||
if (webSocket !== undefined) {
|
||||
|
@ -38,8 +38,6 @@ const AccessGatewayModal = ({title, visible, handleOk, handleCancel, confirmLoad
|
||||
.then(values => {
|
||||
form.resetFields();
|
||||
handleOk(values);
|
||||
})
|
||||
.catch(info => {
|
||||
});
|
||||
}}
|
||||
onCancel={handleCancel}
|
||||
@ -102,10 +100,6 @@ const AccessGatewayModal = ({title, visible, handleOk, handleCancel, confirmLoad
|
||||
</Form.Item>
|
||||
</>
|
||||
}
|
||||
|
||||
<Form.Item label="本地映射地址" name='localhost' tooltip='隧道映射到本地的地址,请确保Guacd可以访问到此IP'>
|
||||
<Input placeholder="localhost"/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
|
@ -284,6 +284,7 @@ class Asset extends Component {
|
||||
asset['ignore-cert'] = asset['ignore-cert'] === 'true';
|
||||
asset['enable-drive'] = asset['enable-drive'] === 'true';
|
||||
asset['socks-proxy-enable'] = asset['socks-proxy-enable'] === 'true';
|
||||
asset['force-lossless'] = asset['force-lossless'] === 'true';
|
||||
|
||||
this.setState({
|
||||
modalTitle: title,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Col,
|
||||
Collapse,
|
||||
Form,
|
||||
@ -239,20 +240,29 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
|
||||
|
||||
{
|
||||
accountType === 'credential' ?
|
||||
<Form.Item label="授权凭证" name='credentialId'
|
||||
rules={[{required: true, message: '请选择授权凭证'}]}>
|
||||
<Select onChange={() => null}>
|
||||
{credentials.map(item => {
|
||||
return (
|
||||
<Option key={item.id} value={item.id}>
|
||||
<Tooltip placement="topLeft" title={item.name}>
|
||||
{item.name}
|
||||
</Tooltip>
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<>
|
||||
{protocol === 'ssh' ?
|
||||
<Form.Item wrapperCol={{offset: 6}}>
|
||||
<Alert
|
||||
message="GUACD 对ED25519、RSA等密钥类型支持不完善,请选择原生模式进行连接。"
|
||||
type="info"
|
||||
/>
|
||||
</Form.Item> : null}
|
||||
<Form.Item label="授权凭证" name='credentialId'
|
||||
rules={[{required: true, message: '请选择授权凭证'}]}>
|
||||
<Select onChange={() => null}>
|
||||
{credentials.map(item => {
|
||||
return (
|
||||
<Option key={item.id} value={item.id}>
|
||||
<Tooltip placement="topLeft" title={item.name}>
|
||||
{item.name}
|
||||
</Tooltip>
|
||||
</Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</>
|
||||
: null
|
||||
}
|
||||
|
||||
@ -274,6 +284,13 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
|
||||
{
|
||||
accountType === 'private-key' ?
|
||||
<>
|
||||
<Form.Item wrapperCol={{offset: 6}}>
|
||||
<Alert
|
||||
message="GUACD 对ED25519、RSA等密钥类型支持不完善,请选择原生模式进行连接。"
|
||||
type="info"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="授权账户" name='username'>
|
||||
<Input placeholder="输入授权账户"/>
|
||||
</Form.Item>
|
||||
@ -321,11 +338,39 @@ const AssetModal = function ({title, visible, handleOk, handleCancel, confirmLoa
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={11}>
|
||||
<Collapse defaultActiveKey={['remote-app', '认证', 'VNC中继', 'storage', '模式设置', '显示设置', '控制终端行为', 'socks']}
|
||||
ghost>
|
||||
<Collapse
|
||||
defaultActiveKey={['remote-app', '认证', 'VNC中继', 'storage', '模式设置', '显示设置', '控制终端行为', 'socks']}
|
||||
ghost>
|
||||
{
|
||||
protocol === 'rdp' ?
|
||||
<>
|
||||
<Panel header={<Text strong>显示设置</Text>} key="显示设置">
|
||||
<Form.Item
|
||||
name="color-depth"
|
||||
label="色彩深度"
|
||||
initialValue=""
|
||||
>
|
||||
<Select onChange={null}>
|
||||
<Option value="">默认</Option>
|
||||
<Option value="16">低色(16位)</Option>
|
||||
<Option value="24">真彩(24位)</Option>
|
||||
<Option value="32">真彩(32位)</Option>
|
||||
<Option value="8">256色</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="force-lossless"
|
||||
label="无损压缩"
|
||||
valuePropName="checked"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭"/>
|
||||
</Form.Item>
|
||||
</Panel>
|
||||
<Panel header={<Text strong>认证</Text>} key="认证">
|
||||
<Form.Item
|
||||
name="domain"
|
||||
|
@ -131,7 +131,7 @@ class Dashboard extends Component {
|
||||
<div style={{margin: 16, marginBottom: 0}}>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
<Card bordered={true} hoverable={true}>
|
||||
<Card bordered={true} hoverable>
|
||||
<Link to={'/user'}>
|
||||
<Statistic title="在线用户" value={this.state.counter['user']}
|
||||
prefix={<UserOutlined/>}/>
|
||||
@ -139,7 +139,7 @@ class Dashboard extends Component {
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={true} hoverable={true}>
|
||||
<Card bordered={true} hoverable>
|
||||
<Link to={'/asset'}>
|
||||
<Statistic title="资产数量" value={this.state.counter['asset']}
|
||||
prefix={<DesktopOutlined/>}/>
|
||||
@ -147,7 +147,7 @@ class Dashboard extends Component {
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={true} hoverable={true}>
|
||||
<Card bordered={true} hoverable>
|
||||
<Link to={'/credential'} hoverable>
|
||||
<Statistic title="授权凭证" value={this.state.counter['credential']}
|
||||
prefix={<IdcardOutlined/>}/>
|
||||
@ -156,7 +156,7 @@ class Dashboard extends Component {
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card bordered={true} hoverable={true}>
|
||||
<Card bordered={true} hoverable>
|
||||
<Link to={'/online-session'}>
|
||||
<Statistic title="在线会话" value={this.state.counter['onlineSession']}
|
||||
prefix={<LinkOutlined/>}/>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Alert, Button, Form, Input, Layout, Select, Space, Switch, Tabs, Tooltip, Typography} from "antd";
|
||||
import {Alert, Button, Form, Input, Layout, Select, Space, Switch, Tabs, Typography} from "antd";
|
||||
import request from "../../common/request";
|
||||
import {message} from "antd/es";
|
||||
import {ExclamationCircleOutlined} from "@ant-design/icons";
|
||||
import {download, getToken} from "../../utils/utils";
|
||||
import {server} from "../../common/env";
|
||||
|
||||
@ -24,7 +23,9 @@ const formTailLayout = {
|
||||
class Setting extends Component {
|
||||
|
||||
state = {
|
||||
properties: {}
|
||||
refs: [],
|
||||
properties: {},
|
||||
ldapUserSyncLoading: false
|
||||
}
|
||||
|
||||
rdpSettingFormRef = React.createRef();
|
||||
@ -32,10 +33,18 @@ class Setting extends Component {
|
||||
vncSettingFormRef = React.createRef();
|
||||
guacdSettingFormRef = React.createRef();
|
||||
mailSettingFormRef = React.createRef();
|
||||
ldapSettingFormRef = React.createRef();
|
||||
logSettingFormRef = React.createRef();
|
||||
|
||||
componentDidMount() {
|
||||
this.getProperties();
|
||||
// eslint-disable-next-line no-extend-native
|
||||
String.prototype.bool = function () {
|
||||
return (/^true$/i).test(this);
|
||||
};
|
||||
|
||||
this.setState({
|
||||
refs: [this.rdpSettingFormRef, this.sshSettingFormRef, this.vncSettingFormRef, this.guacdSettingFormRef, this.mailSettingFormRef, this.logSettingFormRef]
|
||||
}, this.getProperties)
|
||||
}
|
||||
|
||||
changeProperties = async (values) => {
|
||||
@ -49,11 +58,6 @@ class Setting extends Component {
|
||||
|
||||
getProperties = async () => {
|
||||
|
||||
// eslint-disable-next-line no-extend-native
|
||||
String.prototype.bool = function () {
|
||||
return (/^true$/i).test(this);
|
||||
};
|
||||
|
||||
let result = await request.get('/properties');
|
||||
if (result['code'] === 1) {
|
||||
let properties = result['data'];
|
||||
@ -74,28 +78,10 @@ class Setting extends Component {
|
||||
properties: properties
|
||||
})
|
||||
|
||||
if (this.rdpSettingFormRef.current) {
|
||||
this.rdpSettingFormRef.current.setFieldsValue(properties)
|
||||
}
|
||||
|
||||
if (this.sshSettingFormRef.current) {
|
||||
this.sshSettingFormRef.current.setFieldsValue(properties)
|
||||
}
|
||||
|
||||
if (this.vncSettingFormRef.current) {
|
||||
this.vncSettingFormRef.current.setFieldsValue(properties)
|
||||
}
|
||||
|
||||
if (this.guacdSettingFormRef.current) {
|
||||
this.guacdSettingFormRef.current.setFieldsValue(properties)
|
||||
}
|
||||
|
||||
if (this.mailSettingFormRef.current) {
|
||||
this.mailSettingFormRef.current.setFieldsValue(properties)
|
||||
}
|
||||
|
||||
if (this.logSettingFormRef.current) {
|
||||
this.logSettingFormRef.current.setFieldsValue(properties)
|
||||
for (let ref of this.state.refs) {
|
||||
if (ref.current) {
|
||||
ref.current.setFieldsValue(properties)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
message.error(result['message']);
|
||||
@ -135,6 +121,26 @@ class Setting extends Component {
|
||||
reader.readAsText(files[0]);
|
||||
}
|
||||
|
||||
ldapUserSync = async () => {
|
||||
const id = 'ldap-user-sync'
|
||||
try {
|
||||
this.setState({
|
||||
ldapUserSyncLoading: true
|
||||
});
|
||||
message.info({content: '同步中...', key: id, duration: 5});
|
||||
let result = await request.post(`/properties/ldap-user-sync`);
|
||||
if (result.code !== 1) {
|
||||
message.error({content: result.message, key: id, duration: 10});
|
||||
return;
|
||||
}
|
||||
message.success({content: '同步成功。', key: id, duration: 3});
|
||||
} finally {
|
||||
this.setState({
|
||||
ldapUserSyncLoading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
@ -254,20 +260,6 @@ class Setting extends Component {
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="disable-glyph-caching"
|
||||
label="禁用字形缓存"
|
||||
valuePropName="checked"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item {...formTailLayout}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
更新
|
||||
@ -409,18 +401,18 @@ class Setting extends Component {
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label={<Tooltip
|
||||
title="连接到VNC代理(例如UltraVNC Repeater)时要请求的目标主机。">目标主机
|
||||
<ExclamationCircleOutlined/></Tooltip>}
|
||||
{...formItemLayout}
|
||||
name='dest-host'>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
label='目标主机'
|
||||
tooltip='连接到VNC代理(例如UltraVNC Repeater)时要请求的目标主机。'
|
||||
name='dest-host'>
|
||||
<Input placeholder="目标主机"/>
|
||||
</Form.Item>
|
||||
<Form.Item label={<Tooltip
|
||||
title="连接到VNC代理(例如UltraVNC Repeater)时要请求的目标端口。">目标端口
|
||||
<ExclamationCircleOutlined/></Tooltip>}
|
||||
{...formItemLayout}
|
||||
name='dest-port'>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
label='目标端口'
|
||||
tooltip='连接到VNC代理(例如UltraVNC Repeater)时要请求的目标端口。'
|
||||
name='dest-port'>
|
||||
<Input type='number' min={1} max={65535}
|
||||
placeholder='目标端口'/>
|
||||
</Form.Item>
|
||||
@ -457,26 +449,21 @@ class Setting extends Component {
|
||||
})
|
||||
}}/>
|
||||
</Form.Item>
|
||||
{
|
||||
this.state.properties['enable-recording'] === true ?
|
||||
<>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="session-saved-limit"
|
||||
label="会话录屏保存时长"
|
||||
initialValue=""
|
||||
>
|
||||
<Select onChange={null}>
|
||||
<Option value="">永久</Option>
|
||||
<Option value="30">30天</Option>
|
||||
<Option value="60">60天</Option>
|
||||
<Option value="180">180天</Option>
|
||||
<Option value="360">360天</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</> : null
|
||||
}
|
||||
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="session-saved-limit"
|
||||
label="会话录屏保存时长"
|
||||
initialValue=""
|
||||
>
|
||||
<Select onChange={null} disabled={!this.state.properties['enable-recording']}>
|
||||
<Option value="">永久</Option>
|
||||
<Option value="30">30天</Option>
|
||||
<Option value="60">60天</Option>
|
||||
<Option value="180">180天</Option>
|
||||
<Option value="360">360天</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item {...formTailLayout}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
@ -487,6 +474,11 @@ class Setting extends Component {
|
||||
</TabPane>
|
||||
<TabPane tab="邮箱配置" key="mail">
|
||||
<Title level={3}>邮箱配置</Title>
|
||||
<Alert
|
||||
message="配置邮箱后,添加用户将向对方的邮箱发送账号密码。"
|
||||
type="info"
|
||||
style={{marginBottom: 10}}
|
||||
/>
|
||||
<Form ref={this.mailSettingFormRef} name='mail' onFinish={this.changeProperties}
|
||||
layout="vertical">
|
||||
|
||||
@ -534,7 +526,7 @@ class Setting extends Component {
|
||||
>
|
||||
<Input type='email' placeholder="请输入邮箱账号"/>
|
||||
</Form.Item>
|
||||
|
||||
<input type='password' hidden={true} autoComplete='new-password'/>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="mail-password"
|
||||
@ -555,6 +547,7 @@ class Setting extends Component {
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
</TabPane>
|
||||
|
||||
<TabPane tab="日志配置" key="log">
|
||||
|
@ -1,5 +1,20 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Button, Card, Divider, Form, Image, Input, Layout, Modal, Result, Space, Typography} from "antd";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Descriptions,
|
||||
Divider,
|
||||
Form,
|
||||
Image,
|
||||
Input,
|
||||
Layout,
|
||||
Modal,
|
||||
Result,
|
||||
Row,
|
||||
Space,
|
||||
Typography
|
||||
} from "antd";
|
||||
import request from "../../common/request";
|
||||
import {message} from "antd/es";
|
||||
import {ExclamationCircleOutlined, ReloadOutlined} from "@ant-design/icons";
|
||||
@ -7,12 +22,13 @@ import {isAdmin} from "../../service/permission";
|
||||
|
||||
const {Content} = Layout;
|
||||
const {Meta} = Card;
|
||||
const {Title} = Typography;
|
||||
const {Title, Text} = Typography;
|
||||
|
||||
const formItemLayout = {
|
||||
labelCol: {span: 4},
|
||||
wrapperCol: {span: 10},
|
||||
};
|
||||
|
||||
const formTailLayout = {
|
||||
labelCol: {span: 4},
|
||||
wrapperCol: {span: 10, offset: 4},
|
||||
@ -24,17 +40,19 @@ class Info extends Component {
|
||||
state = {
|
||||
user: {
|
||||
enableTotp: false
|
||||
}
|
||||
},
|
||||
accessToken: {}
|
||||
}
|
||||
|
||||
passwordFormRef = React.createRef();
|
||||
|
||||
componentDidMount() {
|
||||
this.loadInfo();
|
||||
this.loadAccessToken();
|
||||
}
|
||||
|
||||
loadInfo = async () => {
|
||||
let result = await request.get('/info');
|
||||
let result = await request.get('/account/info');
|
||||
if (result['code'] === 1) {
|
||||
this.setState({
|
||||
user: result['data']
|
||||
@ -45,6 +63,26 @@ class Info extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
loadAccessToken = async () => {
|
||||
let result = await request.get('/account/access-token');
|
||||
if (result['code'] === 1) {
|
||||
this.setState({
|
||||
accessToken: result['data']
|
||||
})
|
||||
} else {
|
||||
message.error(result['message']);
|
||||
}
|
||||
}
|
||||
|
||||
genAccessToken = async () => {
|
||||
let result = await request.post('/account/access-token');
|
||||
if (result['code'] === 1) {
|
||||
this.loadAccessToken();
|
||||
} else {
|
||||
message.error(result['message']);
|
||||
}
|
||||
}
|
||||
|
||||
onNewPasswordChange(value) {
|
||||
this.setState({
|
||||
'newPassword': value.target.value
|
||||
@ -72,7 +110,7 @@ class Info extends Component {
|
||||
}
|
||||
|
||||
changePassword = async (values) => {
|
||||
let result = await request.post('/change-password', values);
|
||||
let result = await request.post('/account/change-password', values);
|
||||
if (result.code === 1) {
|
||||
message.success('密码修改成功,即将跳转至登录页面');
|
||||
window.location.href = '/#';
|
||||
@ -83,7 +121,7 @@ class Info extends Component {
|
||||
|
||||
confirmTOTP = async (values) => {
|
||||
values['secret'] = this.state.secret
|
||||
let result = await request.post('/confirm-totp', values);
|
||||
let result = await request.post('/account/confirm-totp', values);
|
||||
if (result.code === 1) {
|
||||
message.success('TOTP启用成功');
|
||||
await this.loadInfo();
|
||||
@ -97,7 +135,7 @@ class Info extends Component {
|
||||
}
|
||||
|
||||
resetTOTP = async () => {
|
||||
let result = await request.get('/reload-totp');
|
||||
let result = await request.get('/account/reload-totp');
|
||||
if (result.code === 1) {
|
||||
this.setState({
|
||||
qr: result.data.qr,
|
||||
@ -113,60 +151,82 @@ class Info extends Component {
|
||||
return (
|
||||
<>
|
||||
<Content className={["site-layout-background", contentClassName]}>
|
||||
<Title level={3}>修改密码</Title>
|
||||
<Form ref={this.passwordFormRef} name="password" onFinish={this.changePassword}>
|
||||
<input type='password' hidden={true} autoComplete='new-password'/>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="oldPassword"
|
||||
label="原始密码"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '原始密码',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input type='password' placeholder="请输入原始密码" style={{width: 240}}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="newPassword"
|
||||
label="新的密码"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入新的密码',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input type='password' placeholder="新的密码"
|
||||
onChange={(value) => this.onNewPasswordChange(value)} style={{width: 240}}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="newPassword2"
|
||||
label="确认密码"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请和上面输入新的密码保持一致',
|
||||
},
|
||||
]}
|
||||
validateStatus={this.state.validateStatus}
|
||||
help={this.state.errorMsg || ''}
|
||||
>
|
||||
<Input type='password' placeholder="请和上面输入新的密码保持一致"
|
||||
onChange={(value) => this.onNewPassword2Change(value)} style={{width: 240}}/>
|
||||
</Form.Item>
|
||||
<Form.Item {...formTailLayout}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
提交
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Title level={3}>修改密码</Title>
|
||||
<Form ref={this.passwordFormRef} name="password" onFinish={this.changePassword}>
|
||||
<input type='password' hidden={true} autoComplete='new-password'/>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="oldPassword"
|
||||
label="原始密码"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '原始密码',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input type='password' placeholder="请输入原始密码" style={{width: 240}}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="newPassword"
|
||||
label="新的密码"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入新的密码',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input type='password' placeholder="新的密码"
|
||||
onChange={(value) => this.onNewPasswordChange(value)} style={{width: 240}}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{...formItemLayout}
|
||||
name="newPassword2"
|
||||
label="确认密码"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请和上面输入新的密码保持一致',
|
||||
},
|
||||
]}
|
||||
validateStatus={this.state.validateStatus}
|
||||
help={this.state.errorMsg || ''}
|
||||
>
|
||||
<Input type='password' placeholder="请和上面输入新的密码保持一致"
|
||||
onChange={(value) => this.onNewPassword2Change(value)} style={{width: 240}}/>
|
||||
</Form.Item>
|
||||
<Form.Item {...formTailLayout}>
|
||||
<Button type="primary" htmlType="submit">
|
||||
提交
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Divider/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Title level={3}>授权信息</Title>
|
||||
<Descriptions column={1}>
|
||||
<Descriptions.Item label="授权令牌">
|
||||
<Text strong copyable>{this.state.accessToken.token}</Text>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label="生成时间">
|
||||
<Text strong>{this.state.accessToken.created}</Text>
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Space>
|
||||
<Button type="primary" onClick={this.genAccessToken}>
|
||||
重新生成
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider/>
|
||||
|
||||
<Title level={3}>双因素认证</Title>
|
||||
<Form hidden={this.state.qr}>
|
||||
@ -187,7 +247,7 @@ class Info extends Component {
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
let result = await request.post('/reset-totp');
|
||||
let result = await request.post('/account/reset-totp');
|
||||
if (result.code === 1) {
|
||||
message.success('双因素认证解除成功');
|
||||
await this.loadInfo();
|
||||
|
@ -13,7 +13,7 @@ const {Content} = Layout;
|
||||
const {Title, Text} = Typography;
|
||||
const {Search} = Input;
|
||||
|
||||
const keys = ['upload', 'download', 'delete', 'rename', 'edit'];
|
||||
const keys = ['upload', 'download', 'delete', 'rename', 'edit', 'copy', 'paste'];
|
||||
|
||||
class Strategy extends Component {
|
||||
|
||||
@ -289,6 +289,20 @@ class Strategy extends Component {
|
||||
render: (text) => {
|
||||
return renderStatus(text);
|
||||
}
|
||||
}, {
|
||||
title: '复制',
|
||||
dataIndex: 'copy',
|
||||
key: 'copy',
|
||||
render: (text) => {
|
||||
return renderStatus(text);
|
||||
}
|
||||
}, {
|
||||
title: '粘贴',
|
||||
dataIndex: 'paste',
|
||||
key: 'paste',
|
||||
render: (text) => {
|
||||
return renderStatus(text);
|
||||
}
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'created',
|
||||
|
@ -17,6 +17,8 @@ const StrategyModal = ({title, visible, handleOk, handleCancel, confirmLoading,
|
||||
'delete': false,
|
||||
'rename': false,
|
||||
'edit': false,
|
||||
'copy': false,
|
||||
'paste': false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -32,8 +34,6 @@ const StrategyModal = ({title, visible, handleOk, handleCancel, confirmLoading,
|
||||
.then(values => {
|
||||
form.resetFields();
|
||||
handleOk(values);
|
||||
})
|
||||
.catch(info => {
|
||||
});
|
||||
}}
|
||||
onCancel={handleCancel}
|
||||
@ -59,7 +59,8 @@ const StrategyModal = ({title, visible, handleOk, handleCancel, confirmLoading,
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="编辑" name='edit' rules={[{required: true}]} valuePropName="checked" tooltip={'编辑需要先开启下载'}>
|
||||
<Form.Item label="编辑" name='edit' rules={[{required: true}]} valuePropName="checked"
|
||||
tooltip={'编辑需要先开启下载'}>
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭"/>
|
||||
</Form.Item>
|
||||
|
||||
@ -70,6 +71,14 @@ const StrategyModal = ({title, visible, handleOk, handleCancel, confirmLoading,
|
||||
<Form.Item label="重命名" name='rename' rules={[{required: true}]} valuePropName="checked">
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="复制" name='copy' rules={[{required: true}]} valuePropName="checked">
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="粘贴" name='paste' rules={[{required: true}]} valuePropName="checked">
|
||||
<Switch checkedChildren="开启" unCheckedChildren="关闭"/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
|
@ -450,6 +450,17 @@ class User extends Component {
|
||||
)
|
||||
},
|
||||
sorter: true,
|
||||
}, {
|
||||
title: '来源',
|
||||
dataIndex: 'source',
|
||||
key: 'source',
|
||||
render: (text) => {
|
||||
if (text === 'ldap') {
|
||||
return (
|
||||
<Tag color="gold">域同步</Tag>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
@ -460,6 +471,7 @@ class User extends Component {
|
||||
<Menu>
|
||||
<Menu.Item key="1">
|
||||
<Button type="text" size='small'
|
||||
disabled={record['source'] === 'ldap'}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
changePasswordVisible: true,
|
||||
@ -626,6 +638,7 @@ class User extends Component {
|
||||
</div>
|
||||
|
||||
<Table rowSelection={rowSelection}
|
||||
rowKey='id'
|
||||
dataSource={this.state.items}
|
||||
columns={columns}
|
||||
position={'both'}
|
||||
|
@ -73,11 +73,8 @@ class UserGroup extends Component {
|
||||
} catch (e) {
|
||||
|
||||
} finally {
|
||||
const items = data.items.map(item => {
|
||||
return {'key': item['id'], ...item}
|
||||
})
|
||||
this.setState({
|
||||
items: items,
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
queryParams: queryParams,
|
||||
loading: false
|
||||
@ -95,8 +92,7 @@ class UserGroup extends Component {
|
||||
queryParams: queryParams
|
||||
});
|
||||
|
||||
this.loadTableData(queryParams).then(r => {
|
||||
})
|
||||
this.loadTableData(queryParams);
|
||||
};
|
||||
|
||||
showDeleteConfirm(id, content) {
|
||||
@ -139,7 +135,6 @@ class UserGroup extends Component {
|
||||
}
|
||||
|
||||
await this.handleSearchByNickname('');
|
||||
console.log(model)
|
||||
this.setState({
|
||||
model: model,
|
||||
modalVisible: true,
|
||||
@ -147,7 +142,7 @@ class UserGroup extends Component {
|
||||
});
|
||||
};
|
||||
|
||||
handleCancelModal = e => {
|
||||
handleCancelModal = () => {
|
||||
this.setState({
|
||||
modalVisible: false,
|
||||
modalTitle: '',
|
||||
@ -161,37 +156,43 @@ class UserGroup extends Component {
|
||||
modalConfirmLoading: true
|
||||
});
|
||||
|
||||
if (formData.id) {
|
||||
// 向后台提交数据
|
||||
const result = await request.put('/user-groups/' + formData.id, formData);
|
||||
if (result.code === 1) {
|
||||
message.success('操作成功', 3);
|
||||
try {
|
||||
if (formData.id) {
|
||||
// 向后台提交数据
|
||||
const result = await request.put('/user-groups/' + formData.id, formData);
|
||||
if (result.code === 1) {
|
||||
message.success('操作成功', 3);
|
||||
|
||||
this.setState({
|
||||
modalVisible: false
|
||||
});
|
||||
await this.loadTableData(this.state.queryParams);
|
||||
this.setState({
|
||||
modalVisible: false
|
||||
});
|
||||
await this.loadTableData(this.state.queryParams);
|
||||
return true;
|
||||
} else {
|
||||
message.error(result.message, 10);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
message.error(result.message, 10);
|
||||
}
|
||||
} else {
|
||||
// 向后台提交数据
|
||||
const result = await request.post('/user-groups', formData);
|
||||
if (result.code === 1) {
|
||||
message.success('操作成功', 3);
|
||||
// 向后台提交数据
|
||||
const result = await request.post('/user-groups', formData);
|
||||
if (result.code === 1) {
|
||||
message.success('操作成功', 3);
|
||||
|
||||
this.setState({
|
||||
modalVisible: false
|
||||
});
|
||||
await this.loadTableData(this.state.queryParams);
|
||||
} else {
|
||||
message.error(result.message, 10);
|
||||
this.setState({
|
||||
modalVisible: false
|
||||
});
|
||||
await this.loadTableData(this.state.queryParams);
|
||||
return true;
|
||||
} else {
|
||||
message.error(result.message, 10);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.setState({
|
||||
modalConfirmLoading: false
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({
|
||||
modalConfirmLoading: false
|
||||
});
|
||||
};
|
||||
|
||||
handleSearchByName = name => {
|
||||
@ -280,7 +281,7 @@ class UserGroup extends Component {
|
||||
title: '授权资产',
|
||||
dataIndex: 'assetCount',
|
||||
key: 'assetCount',
|
||||
render: (text, record, index) => {
|
||||
render: (text, record) => {
|
||||
return <Button type='link' onClick={async () => {
|
||||
this.setState({
|
||||
assetVisible: true,
|
||||
@ -292,7 +293,7 @@ class UserGroup extends Component {
|
||||
title: '创建日期',
|
||||
dataIndex: 'created',
|
||||
key: 'created',
|
||||
render: (text, record) => {
|
||||
render: (text) => {
|
||||
return (
|
||||
<Tooltip title={text}>
|
||||
{dayjs(text).fromNow()}
|
||||
@ -328,7 +329,7 @@ class UserGroup extends Component {
|
||||
const selectedRowKeys = this.state.selectedRowKeys;
|
||||
const rowSelection = {
|
||||
selectedRowKeys: this.state.selectedRowKeys,
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
onChange: (selectedRowKeys) => {
|
||||
this.setState({selectedRowKeys});
|
||||
},
|
||||
};
|
||||
@ -408,6 +409,7 @@ class UserGroup extends Component {
|
||||
</div>
|
||||
|
||||
<Table rowSelection={rowSelection}
|
||||
rowKey='id'
|
||||
dataSource={this.state.items}
|
||||
columns={columns}
|
||||
position={'both'}
|
||||
|
@ -27,11 +27,11 @@ const UserGroupModal = ({
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(values => {
|
||||
form.resetFields();
|
||||
handleOk(values);
|
||||
})
|
||||
.catch(info => {
|
||||
.then(async values => {
|
||||
let ok = await handleOk(values);
|
||||
if (ok) {
|
||||
form.resetFields();
|
||||
}
|
||||
});
|
||||
}}
|
||||
onCancel={handleCancel}
|
||||
|
BIN
web/src/images/bg.png
Normal file
BIN
web/src/images/bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 MiB |
14
web/src/images/logo-with-name.svg
Normal file
14
web/src/images/logo-with-name.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg width="400" height="140" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none">
|
||||
<path fill="#40a9ff" d="M 10 10 V 110 L 65 135 L 120 110 V 10 Z"/>
|
||||
<path fill="white" d="M 65 50 l -55 20 v 20 l 55 -20 l 55 20 v -20 l -55 -20 Z"/>
|
||||
<path fill="#0050b3" d="M 65 70 l -55 20 v 20 l 55 25 l 55 -25 v -20 l -55 -20 Z"/>
|
||||
</g>
|
||||
|
||||
<g>
|
||||
<text transform="translate(150 20)" fill="white">
|
||||
<tspan class="name" font-size="48" font-weight="bold" x="0" y="30">NEXT </tspan>
|
||||
<tspan class="name" font-size="48" font-weight="bold" x="0" y="90">TERMINAL</tspan>
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 638 B |
10
web/src/images/logo.svg
Normal file
10
web/src/images/logo.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg width="130" height="140" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none">
|
||||
<path fill="#40a9ff" d="M 10 10 V 110 L 65 135 L 120 110 V 10 Z"/>
|
||||
<g fill="none">
|
||||
<path fill="#40a9ff" d="M 10 10 V 110 L 65 135 L 120 110 V 10 Z"/>
|
||||
<path fill="white" d="M 65 50 l -55 20 v 20 l 55 -20 l 55 20 v -20 l -55 -20 Z"/>
|
||||
<path fill="#0050b3" d="M 65 70 l -55 20 v 20 l 55 25 l 55 -25 v -20 l -55 -20 Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 482 B |
@ -5,6 +5,10 @@ export const sleep = function (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
export const setToken = function (token) {
|
||||
localStorage.setItem('X-Auth-Token', token);
|
||||
}
|
||||
|
||||
export const getToken = function () {
|
||||
return localStorage.getItem('X-Auth-Token');
|
||||
}
|
||||
|
Reference in New Issue
Block a user