修复 「1.2.2 用户管理-用户列表勾选单一用户会全选 」 close #216

This commit is contained in:
dushixiang
2022-01-23 17:53:22 +08:00
parent 29c066ca3a
commit d35b348a33
130 changed files with 5467 additions and 4554 deletions
+47 -6
View File
@@ -1,12 +1,12 @@
{
"name": "next-terminal",
"version": "1.2.0",
"version": "2.0.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "next-terminal",
"version": "1.2.0",
"version": "2.0.5",
"dependencies": {
"@ant-design/charts": "^1.2.13",
"@ant-design/icons": "^4.6.4",
@@ -23,6 +23,7 @@
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.0",
"react-tsparticles": "^1.37.5",
"xterm": "^4.9.0",
"xterm-addon-fit": "^0.4.0",
"xterm-addon-web-links": "^0.4.0"
@@ -16838,8 +16839,8 @@
},
"node_modules/react": {
"version": "16.14.0",
"resolved": "https://registry.npmmirror.com/react/download/react-16.14.0.tgz",
"integrity": "sha1-lNd23dCqo32j7aj8W2sYpMmjEU0=",
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
"dependencies": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@@ -17203,6 +17204,22 @@
"semver": "bin/semver"
}
},
"node_modules/react-tsparticles": {
"version": "1.37.5",
"resolved": "https://registry.npmjs.org/react-tsparticles/-/react-tsparticles-1.37.5.tgz",
"integrity": "sha512-Vl+rg3C+vsrek675x7OHBbLLZLJ0dZZjV3OhjT9NiKhdK+NcxNDB2CL3INsSZIv99sln5DRgsxdL3GYrZtqsqg==",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"tsparticles": "^1.37.5"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/matteobruni"
},
"peerDependencies": {
"react": ">=16"
}
},
"node_modules/read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.nlark.com/read-pkg/download/read-pkg-5.2.0.tgz?cache=0&sync_timestamp=1628984780649&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fread-pkg%2Fdownload%2Fread-pkg-5.2.0.tgz",
@@ -20044,6 +20061,16 @@
"resolved": "https://registry.nlark.com/tslib/download/tslib-2.3.1.tgz",
"integrity": "sha1-6KM1rdXOrlGqJh0ypJAVjvBC7wE="
},
"node_modules/tsparticles": {
"version": "1.37.5",
"resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-1.37.5.tgz",
"integrity": "sha512-BQBRnnKKhKH2POwxuHzPiuL/zPUjuR5QmMCFQ2vm3fadE2k4vqmH099m1R9R+nt0lTvWL6SCS0hYQQkqUjsMXg==",
"hasInstallScript": true,
"funding": {
"type": "github",
"url": "https://github.com/sponsors/matteobruni"
}
},
"node_modules/tsutils": {
"version": "3.21.0",
"resolved": "https://registry.npm.taobao.org/tsutils/download/tsutils-3.21.0.tgz?cache=0&sync_timestamp=1615138426726&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftsutils%2Fdownload%2Ftsutils-3.21.0.tgz",
@@ -35787,8 +35814,8 @@
},
"react": {
"version": "16.14.0",
"resolved": "https://registry.npmmirror.com/react/download/react-16.14.0.tgz",
"integrity": "sha1-lNd23dCqo32j7aj8W2sYpMmjEU0=",
"resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz",
"integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
@@ -36085,6 +36112,15 @@
}
}
},
"react-tsparticles": {
"version": "1.37.5",
"resolved": "https://registry.npmjs.org/react-tsparticles/-/react-tsparticles-1.37.5.tgz",
"integrity": "sha512-Vl+rg3C+vsrek675x7OHBbLLZLJ0dZZjV3OhjT9NiKhdK+NcxNDB2CL3INsSZIv99sln5DRgsxdL3GYrZtqsqg==",
"requires": {
"fast-deep-equal": "^3.1.3",
"tsparticles": "^1.37.5"
}
},
"read-pkg": {
"version": "5.2.0",
"resolved": "https://registry.nlark.com/read-pkg/download/read-pkg-5.2.0.tgz?cache=0&sync_timestamp=1628984780649&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fread-pkg%2Fdownload%2Fread-pkg-5.2.0.tgz",
@@ -38425,6 +38461,11 @@
"resolved": "https://registry.nlark.com/tslib/download/tslib-2.3.1.tgz",
"integrity": "sha1-6KM1rdXOrlGqJh0ypJAVjvBC7wE="
},
"tsparticles": {
"version": "1.37.5",
"resolved": "https://registry.npmjs.org/tsparticles/-/tsparticles-1.37.5.tgz",
"integrity": "sha512-BQBRnnKKhKH2POwxuHzPiuL/zPUjuR5QmMCFQ2vm3fadE2k4vqmH099m1R9R+nt0lTvWL6SCS0hYQQkqUjsMXg=="
},
"tsutils": {
"version": "3.21.0",
"resolved": "https://registry.npm.taobao.org/tsutils/download/tsutils-3.21.0.tgz?cache=0&sync_timestamp=1615138426726&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftsutils%2Fdownload%2Ftsutils-3.21.0.tgz",
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "next-terminal",
"version": "1.2.2",
"version": "1.2.3",
"private": true,
"dependencies": {
"@ant-design/charts": "^1.2.13",
@@ -18,6 +18,7 @@
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.0",
"react-tsparticles": "^1.37.5",
"xterm": "^4.9.0",
"xterm-addon-fit": "^0.4.0",
"xterm-addon-web-links": "^0.4.0"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 134 KiB

+2 -2
View File
@@ -1000,7 +1000,7 @@ g.dispatchEvent=function(a){var b,c=this.ve;if(c)for(b=[];c;c=c.ve)b.push(c);c=t
g.nd=function(){qw.Zd.nd.call(this);if(this.Ib){var a=this.Ib,b=0,c;for(c in a.rb){for(var d=a.rb[c],e=0;e<d.length;e++)++b,Xv(d[e]);delete a.rb[c];a.wd--}}this.ve=null};function rw(a,b,c,d){b=a.Ib.rb[String(b)];if(!b)return!0;b=b.concat();for(var e=!0,f=0;f<b.length;++f){var h=b[f];if(h&&!h.$c&&h.capture==c){var k=h.listener,l=h.Ub||h.src;h.Fd&&$v(a.Ib,h);e=!1!==k.call(l,d)&&e}}return e&&0!=d.af}g.re=function(a,b,c,d){return this.Ib.re(String(a),b,c,d)};function sw(a,b,c){if(ha(a))c&&(a=pa(a,c));else if(a&&"function"==typeof a.handleEvent)a=pa(a.handleEvent,a);else throw Error("Invalid listener argument");return 2147483647<Number(b)?-1:ba.setTimeout(a,b||0)};function tw(){}tw.prototype.Ke=null;function uw(a){var b;(b=a.Ke)||(b={},vw(a)&&(b[0]=!0,b[1]=!0),b=a.Ke=b);return b};var ww;function xw(){}qa(xw,tw);function yw(a){return(a=vw(a))?new ActiveXObject(a):new XMLHttpRequest}function vw(a){if(!a.Te&&"undefined"==typeof XMLHttpRequest&&"undefined"!=typeof ActiveXObject){for(var b=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"],c=0;c<b.length;c++){var d=b[c];try{return new ActiveXObject(d),a.Te=d}catch(e){}}throw Error("Could not create ActiveXObject. ActiveX might be disabled, or MSXML might not be installed");}return a.Te}ww=new xw;function zw(a){qw.call(this);this.headers=new Ma;this.ce=a||null;this.oc=!1;this.be=this.ca=null;this.ue="";this.Ic=this.se=this.Sd=this.qe=!1;this.Ae=0;this.$d=null;this.$e=Aw;this.Be=this.Lf=this.ef=!1}qa(zw,qw);var Aw="",Bw=/^https?$/i,Cw=["POST","PUT"],Dw=[];function Ew(a,b){var c=new zw;Dw.push(c);b&&c.Ib.add("complete",b,!1,void 0,void 0);c.Ib.add("ready",c.gf,!0,void 0,void 0);c.send(a,void 0,void 0,void 0);return c}g=zw.prototype;
g.gf=function(){if(!this.od&&(this.od=!0,this.nd(),0!=xv)){var a=ja(this);delete yv[a]}ya(Dw,this)};
g.send=function(a,b,c,d){if(this.ca)throw Error("[goog.net.XhrIo] Object is active with another request\x3d"+this.ue+"; newUri\x3d"+a);b=b?b.toUpperCase():"GET";this.ue=a;this.qe=!1;this.oc=!0;this.ca=this.ce?yw(this.ce):yw(ww);this.be=this.ce?uw(this.ce):uw(ww);this.ca.onreadystatechange=pa(this.Ye,this);this.Lf&&"onprogress"in this.ca&&(this.ca.onprogress=pa(function(a){this.Xe(a,!0)},this),this.ca.upload&&(this.ca.upload.onprogress=pa(this.Xe,this)));try{this.se=!0,this.ca.open(b,String(a),!0),
this.se=!1}catch(f){Fw(this);return}a=c||"";var e=this.headers.clone();d&&La(d,function(a,b){e.set(b,a)});d=wa(e.Xc());c=ba.FormData&&a instanceof ba.FormData;!(0<=ua(Cw,b))||d||c||e.set("Content-Type","application/x-www-form-urlencoded;charset\x3dutf-8");e.forEach(function(a,b){this.ca.setRequestHeader(b,a)},this);this.$e&&(this.ca.responseType=this.$e);"withCredentials"in this.ca&&this.ca.withCredentials!==this.ef&&(this.ca.withCredentials=this.ef);try{Gw(this),0<this.Ae&&((this.Be=Hw(this.ca))?
this.se=!1}catch(f){Fw(this);return}a=c||"";var e=this.headers.clone();d&&La(d,function(a,b){e.set(b,a)});d=wa(e.Xc());c=ba.FormData&&a instanceof ba.FormData;!(0<=ua(Cw,b))||d||c||e.set("Content-Type","app/x-www-form-urlencoded;charset\x3dutf-8");e.forEach(function(a,b){this.ca.setRequestHeader(b,a)},this);this.$e&&(this.ca.responseType=this.$e);"withCredentials"in this.ca&&this.ca.withCredentials!==this.ef&&(this.ca.withCredentials=this.ef);try{Gw(this),0<this.Ae&&((this.Be=Hw(this.ca))?
(this.ca.timeout=this.Ae,this.ca.ontimeout=pa(this.cf,this)):this.$d=sw(this.cf,this.Ae,this)),this.Sd=!0,this.ca.send(a),this.Sd=!1}catch(f){Fw(this)}};function Hw(a){return Bv&&Lv(9)&&"number"==typeof a.timeout&&void 0!==a.ontimeout}function xa(a){return"content-type"==a.toLowerCase()}g.cf=function(){"undefined"!=typeof aa&&this.ca&&(this.dispatchEvent("timeout"),this.abort(8))};function Fw(a){a.oc=!1;a.ca&&(a.Ic=!0,a.ca.abort(),a.Ic=!1);Iw(a);Jw(a)}
function Iw(a){a.qe||(a.qe=!0,a.dispatchEvent("complete"),a.dispatchEvent("error"))}g.abort=function(){this.ca&&this.oc&&(this.oc=!1,this.Ic=!0,this.ca.abort(),this.Ic=!1,this.dispatchEvent("complete"),this.dispatchEvent("abort"),Jw(this))};g.nd=function(){this.ca&&(this.oc&&(this.oc=!1,this.Ic=!0,this.ca.abort(),this.Ic=!1),Jw(this,!0));zw.Zd.nd.call(this)};g.Ye=function(){this.od||(this.se||this.Sd||this.Ic?Kw(this):this.If())};g.If=function(){Kw(this)};
function Kw(a){if(a.oc&&"undefined"!=typeof aa&&(!a.be[1]||4!=Lw(a)||2!=Mw(a)))if(a.Sd&&4==Lw(a))sw(a.Ye,0,a);else if(a.dispatchEvent("readystatechange"),4==Lw(a)){a.oc=!1;try{var b=Mw(a);a:switch(b){case 200:case 201:case 202:case 204:case 206:case 304:case 1223:var c=!0;break a;default:c=!1}var d;if(!(d=c)){var e;if(e=0===b){var f=String(a.ue).match(Pa)[1]||null;if(!f&&ba.self&&ba.self.location){var h=ba.self.location.protocol;f=h.substr(0,h.length-1)}e=!Bw.test(f?f.toLowerCase():"")}d=e}d?(a.dispatchEvent("complete"),
@@ -1199,7 +1199,7 @@ oy.prototype.qb=function(a,b){var c=null!=b&&(b.m&64||q===b.G)?P(U,b):b,d=D.c(c,
function TA(a){a=null!=a&&(a.m&64||q===a.G)?P(U,a):a;var b=D.c(a,Ol);Tw(b);return K.l(K.l(a,ml,!0),Ol,null)}function UA(a){a=null!=a&&(a.m&64||q===a.G)?P(U,a):a;a=D.c(a,Ol);return t(a)?Je([a]):vi}my.prototype.sb=q;
my.prototype.qb=function(a,b){var c=null!=a&&(a.m&64||q===a.G)?P(U,a):a;D.c(c,jn);var d=null!=b&&(b.m&64||q===b.G)?P(U,b):b,e=D.c(d,jn);c=D.c(d,pi);var f=D.c(d,qi),h=null!=this&&(this.m&64||q===this.G)?P(U,this):this;h=D.c(h,jn);if(G.c(e,h))return d;d=K.A(d,jn,h,be([el,!0]));if(t(h))return t(c)&&(c.B?c.B():c.call(null)),SA(d);t(f)&&(f.B?f.B():f.call(null));return TA(d)};my.prototype.Fe=q;my.prototype.de=function(a,b){return UA(b)};py.prototype.sb=q;
py.prototype.qb=function(a,b){var c=K.l(b,V,V.h(this));c=null!=c&&(c.m&64||q===c.G)?P(U,c):c;var d=D.c(c,Ol);return t(d)?SA(TA(c)):c};py.prototype.Fe=q;py.prototype.de=function(a,b){return UA(b)};function VA(a){return t(a)?(a=ig.c(parseFloat,Fo(""+v.h(a),/:/)),a=ig.l(Ye,cf(a),rg(function(){return function(a){return 60*a}}(a),1)),P(Xe,a)):null}
function WA(a,b,c){t(a)?"string"===typeof a?t(0===a.indexOf("data:application/json;base64,"))?(b=a.substring(29).replace(RegExp("\\s","g"),""),b=JSON.parse(atob(b)),b=fj(b),b=new r(null,1,[V,new r(null,1,[il,b],null)],null)):t(0===a.indexOf("data:text/plain,"))?(a=a.substring(16),b=Ju(Ot(t(b)?b:80,t(c)?c:24),a),b=new r(null,1,[V,b],null)):b=t(0===a.indexOf("npt:"))?new r(null,1,[Zk,VA(a.substring(4))],null):null:b=new r(null,1,[V,new r(null,1,[il,a],null)],null):b=null;return b}
function WA(a,b,c){t(a)?"string"===typeof a?t(0===a.indexOf("data:app/json;base64,"))?(b=a.substring(29).replace(RegExp("\\s","g"),""),b=JSON.parse(atob(b)),b=fj(b),b=new r(null,1,[V,new r(null,1,[il,b],null)],null)):t(0===a.indexOf("data:text/plain,"))?(a=a.substring(16),b=Ju(Ot(t(b)?b:80,t(c)?c:24),a),b=new r(null,1,[V,b],null)):b=t(0===a.indexOf("npt:"))?new r(null,1,[Zk,VA(a.substring(4))],null):null:b=new r(null,1,[V,new r(null,1,[il,a],null)],null):b=null;return b}
var XA=new r(null,2,[pl,new r(null,1,[On,!1],null),il,he],null);
function YA(a,b){var c=null!=b&&(b.m&64||q===b.G)?P(U,b):b,d=D.c(c,no),e=D.l(c,wk,"small"),f=D.l(c,Ak,1),h=D.c(c,Hk),k=D.c(c,fl),l=D.c(c,rl),p=D.l(c,cm,!1),m=D.l(c,gm,"asciinema"),u=D.c(c,qm),w=D.c(c,Bm),x=D.l(c,vm,!1),C=D.l(c,Em,!1),F=function(){var a=VA(h);return t(a)?a:0}();w=WA(w,k,d);var I=null!=w&&(w.m&64||q===w.G)?P(U,w):w;w=D.c(I,V);I=D.c(I,Zk);var M=t(I)?I:wb(w)&&0<F?F:null;I=function(){var b=Pe([Ak,Hk,fl,rl,cm,qm,vm,Em,Mm,no],[f,F,k,l,p,u,x,C,M,d]);return xj.c?xj.c(a,b):xj.call(null,a,b)}();
return hi.A(be([Pe([Uj,V,wk,Ak,Gk,el,fl,wl,Gl,Ol,gm,Hm,jn,no],[F,t(w)?w:XA,e,f,!1,!1,k,null,I,null,m,!1,!1,d]),ji(c)]))}function ZA(a,b,c){a="string"===typeof a?document.getElementById(a):a;b=tp.h(P(YA,be([b,c])));c=new R(null,2,5,T,[PA,b],null);qq?oq(c,a,null):pq.call(null,c,a);return b}
+2 -16
View File
@@ -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;*/
+34 -25
View File
@@ -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 ?
<>&nbsp;<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
+89 -6
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
.container div {
.container > div {
margin: 0 auto;
}
+93 -84
View File
@@ -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({
+25 -3
View File
@@ -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>
)
+1
View File
@@ -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,
+61 -16
View File
@@ -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"
+4 -4
View File
@@ -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/>}/>
+68 -75
View File
@@ -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)时要请求的目标主机。">目标主机&nbsp;
<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)时要请求的目标端口。">目标端口&nbsp;
<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">
+121 -61
View File
@@ -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();
+15 -1
View File
@@ -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',
+12 -3
View File
@@ -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>
)
+13
View File
@@ -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'}
+39 -37
View File
@@ -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'}
+5 -5
View File
@@ -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}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

+14
View 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
View 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

+4
View File
@@ -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');
}