Merge pull request #2220 from serghey-rodin/release/1.0.0-6-ui

UI 1.0.0-6 release.
This commit is contained in:
Alexander Osinskii 2021-12-30 22:02:52 +00:00 committed by GitHub
commit 653348242f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 374 additions and 151 deletions

View file

@ -2,5 +2,6 @@
"compilerOptions": {
"baseUrl": "."
},
"include": ["src"]
"include": ["src"],
"exclude": ["node_modules", "build"]
}

View file

@ -11526,6 +11526,15 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-perfect-scrollbar": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/react-perfect-scrollbar/-/react-perfect-scrollbar-1.5.8.tgz",
"integrity": "sha512-bQ46m70gp/HJtiBOF3gRzBISSZn8FFGNxznTdmTG8AAwpxG1bJCyn7shrgjEvGSQ5FJEafVEiosY+ccER11OSA==",
"requires": {
"perfect-scrollbar": "^1.5.0",
"prop-types": "^15.6.1"
}
},
"react-redux": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz",

View file

@ -25,6 +25,7 @@
"react-dom": "^16.10.2",
"react-helmet": "^6.1.0",
"react-html-parser": "^2.0.2",
"react-perfect-scrollbar": "^1.5.8",
"react-redux": "^7.2.1",
"react-router-dom": "^5.1.2",
"react-scripts": "^3.4.1",

View file

@ -1,12 +1,13 @@
import { REFRESH_COUNTERS } from './menuCounterTypes';
import { checkAuth } from 'src/services/session';
import { setAuthToken } from 'src/utils/token';
import { REFRESH_PANEL } from '../Panel/panelTypes';
export const refreshCounters = () => (dispatch, getState) => {
return new Promise((resolve, reject) => {
checkAuth()
.then(res => {
const { data, token } = res.data;
const { data, token, panel } = res.data;
if (token) setAuthToken(token);
@ -17,6 +18,13 @@ export const refreshCounters = () => (dispatch, getState) => {
}
});
dispatch({
type: REFRESH_PANEL,
value: {
panel
}
});
resolve(token);
})
.catch(err => {

View file

@ -0,0 +1 @@
export const REFRESH_PANEL = 'REFRESH_PANEL';

View file

@ -4,6 +4,7 @@ import { resetPassword } from 'src/ControlPanelService/ResetPassword';
import { resetAuthToken, setAuthToken } from 'src/utils/token';
import { REFRESH_COUNTERS } from '../MenuCounters/menuCounterTypes';
import { SET_USER_SESSION } from '../UserSession/userSessionTypes';
import { REFRESH_PANEL } from '../Panel/panelTypes';
const LOGOUT_RESPONSE = 'logged_out';
const LOGOUT_AS_RESPONSE = 'logged_out_as';
@ -19,12 +20,17 @@ export const login = (user, password) => dispatch => {
type: LOGIN,
value: {
token: token || '',
panel,
i18n: i18n || {},
userName: user,
error
},
});
dispatch({
type: REFRESH_PANEL,
value: {
panel
}
});
dispatch({
type: REFRESH_COUNTERS,
value: {
@ -51,11 +57,16 @@ export const reset = ({ user = '', code = '', password = '', password_confirm =
type: RESET_PASSWORD,
value: {
token,
panel,
userName: user,
error
},
});
dispatch({
type: REFRESH_PANEL,
value: {
panel
}
});
dispatch({
type: REFRESH_COUNTERS,
value: {
@ -84,11 +95,16 @@ export const loginAs = username => dispatch => {
value: {
userName: user,
i18n,
panel,
token,
error
}
});
dispatch({
type: REFRESH_PANEL,
value: {
panel
}
});
dispatch({
type: REFRESH_COUNTERS,
value: {
@ -121,12 +137,17 @@ export const logout = () => (dispatch, getState) => {
value: {
userName: '',
token: '',
panel: {},
session: {},
i18n: [],
error,
},
});
dispatch({
type: REFRESH_PANEL,
value: {
panel: {}
}
});
dispatch({
type: REFRESH_COUNTERS,
value: {
@ -144,12 +165,17 @@ export const logout = () => (dispatch, getState) => {
type: LOGGED_OUT_AS,
value: {
userName,
panel,
token: '',
i18n,
error,
},
});
dispatch({
type: REFRESH_PANEL,
value: {
panel
}
});
dispatch({
type: REFRESH_COUNTERS,
value: {
@ -185,11 +211,16 @@ export const checkAuthHandler = () => (dispatch, getState) => {
value: {
userName: user,
i18n,
panel,
token,
error
}
});
dispatch({
type: REFRESH_PANEL,
value: {
panel
}
});
dispatch({
type: REFRESH_COUNTERS,
value: {

View file

@ -18,7 +18,6 @@ import './BackupRestoreSettings.scss';
export default function BackupRestoreSettings(props) {
const { i18n } = useSelector(state => state.session);
const token = localStorage.getItem("token");
const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
const { focusedElement } = useSelector(state => state.mainNavigation);
const dispatch = useDispatch();

View file

@ -23,7 +23,7 @@ $errorColor: #BE5ABF;
div.error,
div.success {
width: max-content !important;
width: fit-content !important;
span {
font-weight: bold;
@ -58,6 +58,7 @@ $errorColor: #BE5ABF;
width: auto !important;
padding: 10px 0;
margin-left: 0;
margin-right: 10px;
}
}
}
@ -211,7 +212,7 @@ $errorColor: #BE5ABF;
label.label-wrapper {
display: flex;
align-items: flex-end;
width: max-content;
width: fit-content;
span {
font-weight: normal;

View file

@ -30,6 +30,7 @@ const Password = ({ defaultValue, onChange = () => { }, id, name, title, showGen
}
setState({ ...state, generatedPassword: result });
onChange(result);
}
const passwordInputHandler = value => {

View file

@ -1,6 +1,6 @@
.content .edit-template.add-firewall {
.toolbar .search-toolbar-name {
width: max-content;
width: fit-content;
}
label.label-wrapper[for=ip] span {

View file

@ -50,7 +50,7 @@ const InternetProtocol = props => {
</Container>
<Container className="c-3 w-35">
<div>{i18n.Owner}: <span className="stat">{data.OWNER}</span></div>
<div>{i18n.Users}: <span className="stat">{data.U_SYS_USERS.replaceAll(',', ', ')}</span></div>
<div>{i18n.Users}: <span className="stat">{data.U_SYS_USERS.replace(/,/g, ', ')}</span></div>
</Container>
</div>
</Container>

View file

@ -55,7 +55,11 @@ export default function AddMailAccount(props) {
newMailDomain['ok_acc'] = 'add';
newMailDomain['token'] = token;
newMailDomain['v_domain'] = props.domain;
newMailDomain['Password'] = newMailDomain['v_password'];
newMailDomain['v_password'] = state.password;
if (!newMailDomain['v_quota']) newMailDomain['v_quota'] = '';
if (!newMailDomain['v_aliases']) newMailDomain['v_aliases'] = '';
if (!newMailDomain['v_fwd']) newMailDomain['v_fwd'] = '';
if (Object.keys(newMailDomain).length !== 0 && newMailDomain.constructor === Object) {
setState({ ...state, loading: true });
@ -195,8 +199,8 @@ export default function AddMailAccount(props) {
<TextInput
title={i18n['Send login credentials to email address']}
name="v_credentials"
id="credentials" />
name="v_send_email"
id="send_email" />
</div>
<div className="buttons-wrapper">

View file

@ -1,6 +1,6 @@
.content .edit-template.add-mail-account {
.search-toolbar-name {
width: max-content;
width: fit-content;
}
form {

View file

@ -63,10 +63,12 @@ export default function EditMailAccount(props) {
if (error_msg) {
setErrorMessage(error_msg);
setOkMessage('');
setState({ ...state, loading: false });
} else {
dispatch(refreshCounters()).then(() => {
setErrorMessage('');
setOkMessage(ok_msg);
setState({ ...state, loading: false });
});
}
}
@ -200,7 +202,7 @@ export default function EditMailAccount(props) {
</div>
<div className="buttons-wrapper">
<button type="submit" className="add">{i18n.Add}</button>
<button type="submit" className="add">{i18n.Save}</button>
<button type="button" className="back" onClick={goBack}>{i18n.Back}</button>
</div>
</form>

View file

@ -46,6 +46,24 @@ export default function MailInfoBlock({ webMail, hostName, domain, userName = ''
);
}
const getCredentials = () => {
let result = '';
result += `${i18n['Username']}:${userName}@${domain}\n`;
result += `${i18n['Password']}:${password}\n`;
result += `${i18n['IMAP hostname']}:${state.imapHostName}\n`;
result += `${i18n['IMAP port']}:${state.imapPort}\n`;
result += `${i18n['IMAP security']}:${state.imapEncryption}\n`;
result += `${i18n['IMAP auth method']}:${i18n['Normal password']}\n`;
result += `${i18n['SMTP hostname']}:${state.smtpHostName}\n`;
result += `${i18n['SMTP port']}:${state.smtpPort}\n`;
result += `${i18n['SMTP security']}:${state.smtpEncryption}\n`;
result += `${i18n['SMTP auth method']}:${i18n['Normal password']}\n`;
result += `${i18n['Webmail URL']}:${`http://${window.location.hostname}${webMail}`}\n`;
return result;
}
return (
<div className="mail-info-block">
<div class="form-group select-group">
@ -109,15 +127,7 @@ export default function MailInfoBlock({ webMail, hostName, domain, userName = ''
<span><Link to={{ pathname: `http://${window.location.hostname}${webMail}` }} target="_blank">{webMail}</Link></span>
</div>
<input type="hidden" name={i18n['Username']} value={`@${domain}`} />
<input type="hidden" name={i18n['IMAP hostname']} value={state.imapHostName} />
<input type="hidden" name={i18n['SMTP hostname']} value={state.smtpHostName} />
<input type="hidden" name={i18n['IMAP port']} value={state.imapPort} />
<input type="hidden" name={i18n['SMTP port']} value={state.smtpPort} />
<input type="hidden" name={i18n['IMAP security']} value={state.imapEncryption} />
<input type="hidden" name={i18n['SMTP security']} value={state.smtpEncryption} />
<input type="hidden" name={i18n['IMAP auth method']} value={i18n['Normal password']} />
<input type="hidden" name={i18n['SMTP auth method']} value={i18n['Normal password']} />
<input type="hidden" name="v_credentials" value={getCredentials()} />
</div>
</div>
</div>

View file

@ -45,7 +45,7 @@
justify-content: center;
align-items: center;
padding: 0 10px !important;
width: max-content;
width: fit-content;
height: 100%;
text-decoration: none;
color: $black;

View file

@ -14,7 +14,8 @@ const Notifications = () => {
const [loading, setLoading] = useState(false);
useEffect(() => {
if (!notifications) {
if (!notifications.length) {
console.log(notifications);
fetchData();
}
}, [notifications]);
@ -47,7 +48,7 @@ const Notifications = () => {
}
const renderOptions = () => {
if (notifications && notifications.length) {
if (notifications.length) {
return notifications.map(item => {
return (
<>
@ -74,7 +75,7 @@ const Notifications = () => {
<button type="button" className="btn btn-danger dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<div className="bell">
{
notifications && notifications.length
notifications.length
? <BellUnread />
: <Bell />
}

View file

@ -9,7 +9,8 @@ import { Link } from "react-router-dom";
import './Panel.scss';
const Panel = props => {
const { i18n, userName, panel } = useSelector(state => state.session);
const { i18n, userName } = useSelector(state => state.session);
const { panel } = useSelector(state => state.panel);
const { session } = useSelector(state => state.userSession);
const { activeElement, focusedElement } = useSelector(state => state.mainNavigation);
const dispatch = useDispatch();
@ -63,6 +64,40 @@ const Panel = props => {
});
}
const renderNotifications = () => {
if (panel[userName]) {
if (panel[userName]['NOTIFICATIONS'] === 'yes') {
return <Notifications />;
}
}
}
const renderSmallNavigation = () => {
if (document.documentElement.clientWidth < 900) {
return (<div className="top-panel small-device">
<div className="container left-menu">
<div className="logo">
<Link to="/list/user/" onClick={() => dispatch(addActiveElement('/list/user/'))}>
<div>
<img src="/images/white_logo.png" alt="Logo" />
</div>
</Link>
</div>
</div>
<div className="container hamburger" onClick={toggleNavigation}>
<span className="bar"></span>
<span className="bar"></span>
<span className="bar"></span>
</div>
<div className="container profile-menu">
{renderNotifications()}
<div><Link to={`/edit/user?user=${userName}`}>{userName}</Link></div>
<div><button onClick={signOut}>{i18n['Log out']}</button></div>
</div>
</div>);
}
}
return (
<div className="panel-wrapper">
{loading && <Spinner />}
@ -113,7 +148,7 @@ const Panel = props => {
)}
</div>
<div className="container profile-menu">
{panel[userName]['NOTIFICATIONS'] === 'yes' && <Notifications />}
{renderNotifications()}
<div className="edit-user">
<Link to={`/edit/user?user=${userName}`}>
{session.look
@ -130,27 +165,7 @@ const Panel = props => {
</div>
</div>
<div className="top-panel small-device">
<div className="container left-menu">
<div className="logo">
<Link to="/list/user/" onClick={() => dispatch(addActiveElement('/list/user/'))}>
<div>
<img src="/images/white_logo.png" alt="Logo" />
</div>
</Link>
</div>
</div>
<div className="container hamburger" onClick={toggleNavigation}>
<span className="bar"></span>
<span className="bar"></span>
<span className="bar"></span>
</div>
<div className="container profile-menu">
{panel[userName]['NOTIFICATIONS'] === 'yes' && <Notifications />}
<div><Link to={`/edit/user?user=${userName}`}>{userName}</Link></div>
<div><button onClick={signOut}>{i18n['Log out']}</button></div>
</div>
</div>
{renderSmallNavigation()}
</div>
);
}

View file

@ -46,7 +46,7 @@
justify-content: center;
align-items: center;
padding: 0 10px !important;
width: max-content;
width: fit-content;
height: 100%;
text-decoration: none;
color: white;
@ -91,7 +91,8 @@
padding: 0;
justify-content: space-between;
div {
div.top-link,
div.nav-link {
flex: 1 1 auto;
height: 100%;
transform: translateX(-5px);
@ -150,8 +151,8 @@
svg {
border-radius: 30px;
width: 100%;
height: 100%;
width: 40px;
height: 30px;
padding: 3px;
&:hover {
@ -199,7 +200,7 @@
justify-content: start;
> div {
width: max-content;
width: fit-content;
flex: unset;
padding: 0 1rem;
}
@ -212,11 +213,10 @@
}
.profile-menu {
justify-content: space-between;
align-items: center;
> .edit-user {
width: max-content;
width: fit-content;
}
.long-username {
@ -236,7 +236,7 @@
}
}
@media screen and (min-width: $desktopMax) {
@media screen and (max-width: 1350px) {
.top-panel {
padding: 0 10%;
}
@ -248,13 +248,7 @@
}
}
@media screen and (max-width: 1200px) {
.top-panel {
padding: 0 10%;
}
}
@media (max-width: 1065px) {
@media (max-width: 900px) {
.top-panel {
display: none;
}

View file

@ -4,6 +4,7 @@ import { useSelector, useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import './Menu.scss';
import Spinner from 'src/components/Spinner/Spinner';
const className = height => {
if (height === 35) {
@ -27,7 +28,8 @@ const style = ({ menuHeight, mobile }) => {
const Menu = props => {
const { activeElement, focusedElement } = useSelector(state => state.mainNavigation);
const { i18n, panel, userName } = useSelector(state => state.session);
const { i18n, userName } = useSelector(state => state.session);
const { panel } = useSelector(state => state.panel);
const { session } = useSelector(state => state.userSession);
const { user } = useSelector(state => state.menuCounters);
const dispatch = useDispatch();
@ -50,6 +52,8 @@ const Menu = props => {
return `stat ${activeName === activeElement && 'l-active'} ${activeName === focusedElement && 'focus'}`;
}
if (!panel[userName]) return <Spinner />;
return (
<div className="menu-wrapper">
<div className={className(props.menuHeight)} style={{ height: style(props) }}>
@ -62,11 +66,21 @@ const Menu = props => {
? (<>
<div>
<span>{i18n.Disk}:</span>
<span><span className="value">{user.U_DISK} <span className="unit">{panel[session.look]['U_DISK_MEASURE']}</span></span></span>
<span>
<span className="value">
{panel[session.look]['U_DISK']}
<span className="unit">{panel[session.look]['U_DISK_MEASURE']}</span>
</span>
</span>
</div>
<div>
<span>{i18n.Bandwidth}:</span>
<span><span className="value">{user.U_BANDWIDTH} <span className="unit">{panel[session.look]['U_BANDWIDTH_MEASURE']}</span></span></span>
<span>
<span className="value">
{panel[session.look]['U_BANDWIDTH']}
<span className="unit">{panel[session.look]['U_BANDWIDTH_MEASURE']}</span>
</span>
</span>
</div>
</>)
: (<>

View file

@ -99,10 +99,6 @@
}
}
.stat.last {
flex: 1;
}
.l-active {
border-bottom: 3px solid $primary;

View file

@ -23,18 +23,23 @@
flex-wrap: unset;
width: 100%;
align-items: center;
justify-content: flex-end;
}
a.button-extra,
button.button-extra {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
justify-content: flex-start;
padding: 5px 10px;
background: none;
border: none;
padding: 0 0.75rem;
text-decoration: none;
color: rgb(129, 125, 125);
font-size: 14px;
display: flex;
justify-content: center;
align-items: center;
margin-right: 10px;
border-radius: 3px;
@ -152,11 +157,28 @@
}
}
@media (max-width: 1250px) {
.toolbar {
.r-menu {
a.button-extra,
button.button-extra {
max-width: 30%;
}
}
}
}
@media (max-width: 900px) {
.toolbar {
.r-menu {
overflow: scroll;
a.button-extra,
button.button-extra {
overflow: unset;
max-width: 100%;
}
> div {
margin-left: 3rem;
position: relative;
@ -164,6 +186,7 @@
flex-wrap: unset;
width: 100%;
align-items: center;
justify-content: flex-start;
}
.select-wrapper + div {

View file

@ -3,7 +3,7 @@
label.label-wrapper {
display: flex;
align-items: flex-end;
width: max-content;
width: fit-content;
span {
font-weight: normal;

View file

@ -9,6 +9,7 @@ import { useSelector } from 'react-redux';
const Package = props => {
const { data } = props;
const { i18n } = useSelector(state => state.session);
const { session } = useSelector(state => state.userSession);
const printNameServers = servers => {
let serversArray = servers.split(',');
@ -50,7 +51,7 @@ const Package = props => {
<div className="stats">
<Container className="c-1 w-30">
<div>{i18n['Web Template']}: <span><span className="stat">{data.WEB_TEMPLATE}</span></span></div>
<div>{i18n['Proxy Template']}: <span><span className="stat">{data.PROXY_TEMPLATE}</span></span></div>
{session.PROXY_SYSTEM && <div>{i18n['Proxy Template']}: <span><span className="stat">{data.PROXY_TEMPLATE}</span></span></div>}
<div>{i18n['DNS Template']}: <span><span className="stat">{data.DNS_TEMPLATE}</span></span></div>
<div>{i18n['SSH Access']}: <span><span className="stat">{data.SHELL}</span></span></div>
<div>{i18n['Web Domains']}: <span><span className="stat">{data.WEB_DOMAINS}</span></span></div>

View file

@ -1,7 +1,7 @@
.content .edit-template.edit-bind9 {
.toolbar {
.search-toolbar-name {
width: max-content;
width: fit-content;
}
}

View file

@ -1,7 +1,7 @@
.content .edit-template.edit-bind9 {
.toolbar {
.search-toolbar-name {
width: max-content;
width: fit-content;
}
}

View file

@ -6,6 +6,7 @@ import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';
const EditDatabaseOption = ({ data, visible }) => {
const { DB_PMA_URL, DB_PGA_URL } = useSelector(state => state.userSession.session);
const { i18n } = useSelector(state => state.session);
const printPhpMyAdminHosts = () => {
@ -85,7 +86,7 @@ const EditDatabaseOption = ({ data, visible }) => {
<>
<TextInput
title={i18n['phpMyAdmin URL']}
value={data.mail_url}
value={DB_PMA_URL}
name="v_mysql_url"
id="mysql_url" />
@ -108,7 +109,7 @@ const EditDatabaseOption = ({ data, visible }) => {
<>
<TextInput
title={i18n['phpPgAdmin URL']}
value={data.pgsql_url}
value={DB_PGA_URL}
name="v_pgsql_url"
id="pgsql_url" />

View file

@ -7,6 +7,7 @@ import { Link } from 'react-router-dom';
import { useSelector } from 'react-redux';
const EditMailOption = ({ data, visible }) => {
const { MAIL_URL } = useSelector(state => state.userSession.session);
const { i18n } = useSelector(state => state.session);
const [mailCertificateSystem, setMailCertificateSystem] = useState(false);
@ -58,7 +59,7 @@ const EditMailOption = ({ data, visible }) => {
<TextInput
title={i18n['Webmail URL']}
name="v_mail_url"
value={data.mail_url}
value={MAIL_URL}
id="mail-url" />
<br /><br />

View file

@ -167,12 +167,6 @@ const EditServer = props => {
name="v_language"
id="language" />
{/* <TextInput
value={state.data.port}
title={i18n['Default Port'] ?? 'Default Port'}
name="port"
id="port" /> */}
<div className="modules">
<button type="button" onClick={() => toggleOption('webOption')}>
{i18n['WEB']}

View file

@ -111,7 +111,7 @@
padding: 7px 15px;
text-transform: capitalize;
text-decoration: none;
width: max-content;
width: fit-content;
margin-right: 10px;
&:hover {

View file

@ -4,7 +4,7 @@ $secondaryLight: #f8b014;
.content .edit-template.edit-httpd {
.toolbar {
.search-toolbar-name {
width: max-content;
width: fit-content;
a {
color: $secondary;
@ -17,7 +17,7 @@ $secondaryLight: #f8b014;
}
.link {
width: max-content;
width: fit-content;
margin-left: 15px;
a {

View file

@ -1,7 +1,7 @@
.content .edit-template.edit-mysql {
.toolbar {
.search-toolbar-name {
width: max-content;
width: fit-content;
}
}

View file

@ -3,11 +3,11 @@ $secondaryLight: #f8b014;
.content .edit-template.edit-nginx {
.toolbar {
.search-toolbar-name {
width: max-content;
width: fit-content;
}
.link {
width: max-content;
width: fit-content;
margin-left: 15px;
a {

View file

@ -5,13 +5,13 @@ $textColor: #555;
.content .edit-template.edit-php {
.toolbar {
.search-toolbar-name {
width: max-content;
width: fit-content;
color: $textColor;
}
.search-toolbar-name,
.link {
width: max-content;
width: fit-content;
a {
text-decoration: none;

View file

@ -1,7 +1,7 @@
.content .edit-template.edit-pgsql {
.toolbar {
.search-toolbar-name {
width: max-content;
width: fit-content;
}
}

View file

@ -1,7 +1,7 @@
.content .edit-template.edit-service {
.toolbar {
.search-toolbar-name {
width: max-content;
width: fit-content;
}
}

View file

@ -4,6 +4,7 @@ import { logout } from 'src/actions/Session/sessionActions';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import Spinner from '../Spinner/Spinner';
import PerfectScrollbar from 'react-perfect-scrollbar';
import './TopPanel.scss';
@ -82,9 +83,10 @@ const TopPanel = ({ menuItems = [], extraMenuItems = [] }) => {
</Link>
</div>
<PerfectScrollbar>
{renderMenuItems()}
{renderExtraMenuItems()}
</PerfectScrollbar>
</div>
<div className="container profile-menu">

View file

@ -16,6 +16,24 @@
}
.left-menu {
justify-content: unset;
.logo {
width: fit-content;
flex: unset;
}
.scrollbar-container {
&:hover {
background: none;
}
.nav-link {
flex: unset;
padding: 0 5px;
}
}
a, button {
padding: 0 !important;
outline: none;

View file

@ -29,7 +29,7 @@
span.stat.email{
display: block;
width: max-content;
width: fit-content;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
@ -208,7 +208,7 @@ span.stat.email{
@media (max-width: 850px) {
> div {
width: max-content;
width: fit-content;
a, button {
width: 100%;

View file

@ -20,7 +20,8 @@ import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
import HtmlParser from 'react-html-parser';
const AddWebDomain = props => {
const { i18n, panel, userName } = useSelector(state => state.session);
const { i18n, userName } = useSelector(state => state.session);
const { panel } = useSelector(state => state.panel);
const { session } = useSelector(state => state.userSession);
const dispatch = useDispatch();
const token = localStorage.getItem("token");

View file

@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import TextArea from 'src/components/ControlPanel/AddItemLayout/Form/TextArea/TextArea';
import './SslSupport.scss';
@ -30,7 +29,6 @@ const SslSupport = props => {
id="ssl-certificate"
name="v_ssl_crt"
title={i18n['SSL Certificate']}
value={props.sslCertificate}
disabled={letsEncrypt}
optionalTitle={<>/ <button type="button" onClick={() => props.setModalVisible(true)} className="generate-csr">{i18n['Generate CSR']}</button></>} />

View file

@ -13,7 +13,7 @@
}
&:nth-child(2) {
width: max-content;
width: fit-content;
}
}
}

View file

@ -72,7 +72,7 @@ export default function WebDomain(props) {
<Container className="r-col w-85">
<div className="name">
<div>{data.NAME}</div>
<div><span className="dns-name-span">{data.ALIAS.replaceAll(',', ', ')}</span></div>
<div><span className="dns-name-span">{data.ALIAS.replace(/,/g, ', ')}</span></div>
</div>
<div>{data.IP}</div>
<div className="stats">

View file

@ -19,12 +19,16 @@
.name {
display: flex;
align-items: center;
> div:nth-child(1) {
margin-right: 2rem;
}
> div + div {
line-height: 10px;
margin-top: .75rem;
}
.dns-name-span {
font-style: italic;
color: #858585;

View file

@ -106,7 +106,7 @@ const App = () => {
exact
component={Preview} />
<AuthenticatedRoute
path="/list/server/:service"
path="/list/server/service/"
authenticated={session.userName}
component={ServiceInfo} />
<AuthenticatedRoute

View file

@ -380,7 +380,7 @@ export default function MailAccounts(props) {
<LeftButton name={i18n['Add Mail Account']} href={`/add/mail/?domain=${props.domain}`} showLeftMenu={true} />
<div className="r-menu">
<div className="input-group input-group-sm">
<a href={state.webMail} className="button-extra" type="submit">{i18n['open webmail']}</a>
{state.webMail && <a href={state.webMail} className="button-extra" type="submit">{i18n['open webmail']}</a>}
<Checkbox toggleAll={toggleAll} toggled={state.toggledAll} />
<Select list='mailList' bulkAction={bulk} />
<DropdownFilter changeSorting={changeSorting} sorting={state.sorting} order={state.order} list="mailAccountList" />

View file

@ -17,7 +17,7 @@
div.list-item {
.r-col {
.stat.email {
width: max-content;
width: fit-content;
text-transform: none;
}
}

View file

@ -319,7 +319,7 @@ const Servers = props => {
<LeftButton href="/edit/server/" list="server" name={i18n.configure} showLeftMenu={true} />
<div className="r-menu">
<div className="input-group input-group-sm">
<Link to="/list/server/cpu" className="button-extra">{i18n['show: CPU / MEM / NET / DISK']}</Link>
<Link to="/list/server/service/?srv=cpu" className="button-extra">{i18n['show: CPU / MEM / NET / DISK']}</Link>
<Checkbox toggleAll={toggleAll} toggled={state.toggledAll} />
<Select list='serverList' bulkAction={bulk} />
<SearchInput handleSearchTerm={term => props.changeSearchTerm(term)} />

View file

@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import { addActiveElement } from 'src/actions/MainNavigation/mainNavigationActions';
import TopPanel from 'src/components/TopPanel/TopPanel';
import { useParams, useHistory } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { getServiceLogs } from 'src/ControlPanelService/Server';
@ -11,13 +11,12 @@ import { Helmet } from 'react-helmet';
import ReactHtmlParser from 'react-html-parser';
import './styles.scss';
import QueryString from 'qs';
const ServiceInfo = () => {
const { i18n, userName } = useSelector(state => state.session);
const dispatch = useDispatch();
const { activeElement } = useSelector(state => state.mainNavigation);
const history = useHistory();
const { service } = useParams();
const [state, setState] = useState({
data: "",
loading: false
@ -30,14 +29,28 @@ const ServiceInfo = () => {
}, [userName]);
useEffect(() => {
fetchData();
dispatch(addActiveElement(`/list/server/${service}`));
}, [activeElement]);
let queryParams = QueryString.parse(history.location.search, { ignoreQueryPrefix: true });
const fetchData = () => {
if (!queryParams.srv) {
fetchData('cpu');
dispatch(addActiveElement('/list/server/service/?srv=cpu'));
return;
}
if (!menuItems.find(item => item.service === queryParams.srv)) {
dispatch(addActiveElement('/list/server/service/?srv=cpu'));
history.push('/list/server/service/?srv=cpu');
return;
}
fetchData(queryParams.srv);
dispatch(addActiveElement(`/list/server/service/?srv=${queryParams.srv}`));
}, [history.location.search]);
const fetchData = serviceName => {
setState({ ...state, loading: true });
getServiceLogs(service)
getServiceLogs(serviceName)
.then(result => {
setState({ ...state, data: result.data.service_log, loading: false });
})
@ -49,35 +62,43 @@ const ServiceInfo = () => {
const menuItems = [
{
route: '/list/server/cpu',
route: '/list/server/service/?srv=cpu',
service: 'cpu',
name: i18n['CPU']
},
{
route: '/list/server/mem',
route: '/list/server/service/?srv=mem',
service: 'mem',
name: i18n['MEMORY']
},
{
route: '/list/server/disk',
route: '/list/server/service/?srv=disk',
service: 'disk',
name: i18n['DISK']
},
{
route: '/list/server/net',
route: '/list/server/service/?srv=net',
service: 'net',
name: i18n['NETWORK']
},
{
route: '/list/server/web',
route: '/list/server/service/?srv=web',
service: 'web',
name: i18n['WEB']
},
{
route: '/list/server/dns',
route: '/list/server/service/?srv=dns',
service: 'dns',
name: i18n['DNS']
},
{
route: '/list/server/mail',
route: '/list/server/service/?srv=mail',
service: 'mail',
name: i18n['MAIL']
},
{
route: '/list/server/db',
route: '/list/server/service/?srv=db',
service: 'db',
name: i18n['DB']
}
];
@ -93,7 +114,7 @@ const ServiceInfo = () => {
state.loading
? <Spinner />
: (<pre>
{state.data.length && state.data.map(line => (<>{ReactHtmlParser(line)}<br /></>))}
{state.data && ReactHtmlParser(state.data)}
</pre>)
}
</div>

View file

@ -1,7 +1,36 @@
.service-info {
.App .service-info {
@media screen and (max-width: 1066px) {
.top-panel {
display: flex;
}
}
@media screen and (min-width: 1200px) {
.top-panel {
padding: 0 13%;
}
}
.content {
font-size: 14px;
color: #555;
padding-top: 4rem;
@media screen and (min-width: 1067px) {
padding-top: 5rem !important;
margin-top: 0px !important;
}
@media screen and (max-width: 1066px) {
padding-top: 5rem !important;
margin-top: 0px !important;
}
table {
td,th {
padding: 5px 10px;
border: 1px solid black;
}
}
}
}

View file

@ -17,12 +17,16 @@ import { useDispatch, useSelector } from 'react-redux';
import { Helmet } from 'react-helmet';
import './Web.scss';
import { refreshCounters } from 'src/actions/MenuCounters/menuCounterActions';
import { useHistory } from 'react-router-dom';
const Web = props => {
const { i18n } = useSelector(state => state.session);
const { controlPanelFocusedElement } = useSelector(state => state.controlPanelContent);
const { focusedElement } = useSelector(state => state.mainNavigation);
const { panel } = useSelector(state => state.panel);
const { userName } = useSelector(state => state.session);
const dispatch = useDispatch();
const history = useHistory();
const [loading, setLoading] = useState(false);
const [modal, setModal] = useState({
text: '',
@ -40,6 +44,10 @@ const Web = props => {
});
useEffect(() => {
if (panel[userName]['WEB_DOMAINS'] === '0') {
return history.push('/');
}
dispatch(addActiveElement('/list/web/'));
dispatch(removeFocusedElement());
dispatch(removeControlPanelContentFocusedElement());

View file

@ -94,7 +94,7 @@ export default function WebLogs() {
</Helmet>
<TopPanel menuItems={menuItems} extraMenuItems={extraMenuItems} />
<div className="content">
<h6>{state.prefix}</h6>
<h6><b>{state.prefix}</b></h6>
<br />
{
state.loading

View file

@ -1,12 +1,30 @@
.web-logs {
.App .web-logs {
.top-panel {
.left-menu {
.logo {
justify-content: start;
margin: 0;
a {
padding: 0 !important;
}
}
}
}
.content {
font-size: 14px;
color: #555;
padding-top: 4rem;
@media screen and (min-width: 1067px) {
padding-top: 5rem !important;
margin-top: 0px !important;
}
.nav-link:nth-child(3),
.nav-link:nth-child(4) {
width: 9rem;
@media screen and (max-width: 1066px) {
padding-top: 5rem !important;
margin-top: 0px !important;
}
}
}

View file

@ -1,7 +1,7 @@
import { ADD_NOTIFICATIONS, REMOVE_NOTIFICATIONS } from 'src/actions/Notification/notificationTypes';
const INITIAL_STATE = {
notifications: null
notifications: []
};
const notificationReducer = (state = INITIAL_STATE, action) => {

View file

@ -0,0 +1,19 @@
import { REFRESH_PANEL } from '../../actions/Panel/panelTypes';
const INITIAL_STATE = {
panel: {}
};
const panelReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case REFRESH_PANEL:
return {
...state,
panel: action.value.panel
};
default: return state;
}
};
export default panelReducer;

View file

@ -4,8 +4,7 @@ const INITIAL_STATE = {
token: '',
error: '',
i18n: {},
userName: '',
panel: {}
userName: ''
};
const sessionReducer = (state = INITIAL_STATE, action) => {
@ -16,7 +15,6 @@ const sessionReducer = (state = INITIAL_STATE, action) => {
token: action.value.token,
userName: action.value.userName,
i18n: action.value.i18n || {},
panel: action.value.panel,
error: action.value.error
};
@ -26,7 +24,6 @@ const sessionReducer = (state = INITIAL_STATE, action) => {
token: action.value.token,
userName: action.value.userName,
i18n: action.value.i18n || {},
panel: action.value.panel,
error: action.value.error
};
@ -36,7 +33,6 @@ const sessionReducer = (state = INITIAL_STATE, action) => {
token: action.value.token,
userName: action.value.userName,
i18n: action.value.i18n || {},
panel: action.value.panel,
error: action.value.error
};
@ -45,7 +41,6 @@ const sessionReducer = (state = INITIAL_STATE, action) => {
token: action.value.token,
userName: action.value.userName,
i18n: action.value.i18n || {},
panel: action.value.panel,
error: action.value.error
};

View file

@ -5,6 +5,7 @@ import notificationReducer from './Notification/notificationReducer';
import menuCounterReducer from './MenuCounters/menuCounterReducer';
import userSessionReducer from './UserSession/userSessionReducer';
import sessionReducer from './Session/sessionReducer';
import panelReducer from './Panel/panel';
export default combineReducers({
mainNavigation: mainNavigationReducer,
@ -13,4 +14,5 @@ export default combineReducers({
menuCounters: menuCounterReducer,
userSession: userSessionReducer,
session: sessionReducer,
panel: panelReducer,
});