Егор Ермохин
4 years ago
46 changed files with 4035 additions and 2548 deletions
@ -0,0 +1,9 @@
|
||||
root = true |
||||
|
||||
[*] |
||||
end_of_line = lf |
||||
insert_final_newline = true |
||||
charset = utf-8 |
||||
indent_style = space |
||||
indent_size = 4 |
||||
trim_trailing_whitespace = true |
@ -0,0 +1,15 @@
|
||||
{ |
||||
// Use IntelliSense to learn about possible attributes. |
||||
// Hover to view descriptions of existing attributes. |
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
||||
"version": "0.2.0", |
||||
"configurations": [ |
||||
{ |
||||
"type": "chrome", |
||||
"request": "launch", |
||||
"name": "Launch Chrome against localhost", |
||||
"url": "http://localhost:3000", |
||||
"webRoot": "${workspaceFolder}" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,4 @@
|
||||
#!/bin/bash |
||||
ssh bastion 'mv /usr/share/nginx/html/govno /usr/share/nginx/html/govno_bak_$(date +"%y_%m_%d__%H_%M_%S")' |
||||
scp -r build bastion:/usr/share/nginx/html |
||||
ssh bastion 'mv /usr/share/nginx/html/build /usr/share/nginx/html/govno' |
@ -1,26 +1,107 @@
|
||||
import React from 'react'; |
||||
import logo from './logo.svg'; |
||||
import './App.css'; |
||||
import React, { useEffect } from 'react'; |
||||
import TopNavbar from './components/TopNavbar'; |
||||
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom' |
||||
import Login from './components/Login'; |
||||
import Objects from './components/Objects'; |
||||
import PrivateRoute from './components/PrivateRoute'; |
||||
import UnAuthPage from './components/UnAuthPage'; |
||||
import AboutServer from './components/AboutServer'; |
||||
import ErrorPage from './components/ErrorPage'; |
||||
import LoginStore from './stores/LoginStore'; |
||||
import Dashboard from './components/Dashboard'; |
||||
import { observer } from 'mobx-react'; |
||||
import UserInfo from './components/UserInfo'; |
||||
import Users from './components/Users'; |
||||
import LeftMenu from './components/LeftMenu'; |
||||
import ServerLogs from './components/ServerLogs'; |
||||
import ServerLog from './components/ServerLog'; |
||||
import FavServers from './components/FavServers'; |
||||
import PermsTest from './components/PermsTest'; |
||||
import LocalStore from './stores/LocalStore'; |
||||
import Categories from './components/Categories'; |
||||
import ObjectTypes from './components/ObjectTypes'; |
||||
import Calls from './components/Calls'; |
||||
import { FocusStyleManager } from "@blueprintjs/core"; |
||||
|
||||
const App = observer(() => { |
||||
FocusStyleManager.onlyShowFocusOnTabs(); |
||||
|
||||
let { isLoggedIn } = LoginStore; |
||||
let { darkTheme } = LocalStore; |
||||
|
||||
// Wrapped in useCallback otherwise it would be recreated each time
|
||||
// this component rerenders, hence triggering useEffect below
|
||||
let changeTheme = () => { |
||||
if (darkTheme) { |
||||
document.body.className = "bp3-dark"; |
||||
} else { |
||||
document.body.className = "bp3-body"; |
||||
} |
||||
} |
||||
|
||||
useEffect(() => { |
||||
changeTheme(); |
||||
}, [darkTheme]); |
||||
|
||||
const containerStyle = { |
||||
display: 'flex', |
||||
height: '100vh', |
||||
flexDirection: 'column', |
||||
} |
||||
|
||||
const wrapperStyle = { |
||||
padding: 20, |
||||
width: '100%', |
||||
display: 'flex', |
||||
flexDirection: 'column' |
||||
} |
||||
|
||||
const menuStyle = { |
||||
display: 'flex', |
||||
height: '100%' |
||||
} |
||||
|
||||
const withMenu = Component => (props) => { |
||||
return <div style={menuStyle}> |
||||
<LeftMenu darkTheme={(localStorage.getItem('theme') === 'dark') ? true : false} /> |
||||
<div style={wrapperStyle}> |
||||
<Component {...props} /> |
||||
</div> |
||||
</div>; |
||||
} |
||||
|
||||
function App() { |
||||
return ( |
||||
<div className="App"> |
||||
<header className="App-header"> |
||||
<img src={logo} className="App-logo" alt="logo" /> |
||||
<p> |
||||
Edit <code>src/App.js</code> and save to reload. |
||||
</p> |
||||
<a |
||||
className="App-link" |
||||
href="https://reactjs.org" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
> |
||||
Learn React |
||||
</a> |
||||
</header> |
||||
|
||||
<Router> |
||||
<div style={containerStyle}> |
||||
<TopNavbar /> |
||||
<Switch> |
||||
<Route exact path="/"> |
||||
{!!isLoggedIn ? withMenu(Dashboard) : <UnAuthPage />} |
||||
</Route> |
||||
<Route path="/login" component={Login} /> |
||||
<Route path="/clogin" component={() => <Login customServer={true} />} /> |
||||
<Route path="/about"><p>about page</p></Route> |
||||
<PrivateRoute exact path="/objects" component={withMenu(Objects)} /> |
||||
<PrivateRoute path="/objects/:catID" component={withMenu(Objects)} /> |
||||
<PrivateRoute path="/info" component={withMenu(AboutServer)} /> |
||||
<PrivateRoute path="/profile" component={withMenu(UserInfo)} /> |
||||
<PrivateRoute path="/users" component={withMenu(Users)} /> |
||||
<PrivateRoute exact path="/logs" component={withMenu(ServerLogs)} /> |
||||
<PrivateRoute path="/servers" component={withMenu(FavServers)} /> |
||||
<PrivateRoute path="/logs/:filename" component={withMenu(ServerLog)} /> |
||||
<PrivateRoute path="/permstest" component={withMenu(PermsTest)} permission={'modDynext'} /> |
||||
<PrivateRoute path="/permstest2" component={withMenu(PermsTest)} permission={'NO_SUCH_PERM'} /> |
||||
<PrivateRoute path="/categories" component={withMenu(Categories)} /> |
||||
<PrivateRoute path="/types" component={withMenu(ObjectTypes)} /> |
||||
<PrivateRoute path="/calls" component={withMenu(Calls)} /> |
||||
<Route path="*" component={ErrorPage} /> |
||||
</Switch> |
||||
</div> |
||||
</Router> |
||||
</div> |
||||
); |
||||
} |
||||
) |
||||
}); |
||||
|
||||
export default App; |
||||
|
@ -0,0 +1,3 @@
|
||||
export function objectIsEmpty (o) { |
||||
return o === null || o === undefined || !Object.keys(o).length; |
||||
} |
@ -0,0 +1,107 @@
|
||||
import React, { useEffect } from 'react'; |
||||
import { HTMLTable, H1, Tab, Tabs, Checkbox } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import Info from '../stores/ServerInfo'; |
||||
import { Link } from 'react-router-dom'; |
||||
|
||||
const AboutServer = observer(props => { |
||||
|
||||
const { counter, getData, serverInfo, instanceSettings, getInstanceSettings } = Info; |
||||
|
||||
useEffect(() => { |
||||
getInstanceSettings(); |
||||
}, []); |
||||
|
||||
const status = <HTMLTable> |
||||
<thead> |
||||
<tr> |
||||
<th>Параметр</th> |
||||
<th>Значение</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr> |
||||
<td>Полное имя</td> |
||||
<td>{serverInfo.ServerNameFull}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Сокращенное имя</td> |
||||
<td>{serverInfo.ServerNameShort}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Версия</td> |
||||
<td>{serverInfo.ServerVersion}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Количество модулей</td> |
||||
<td>{serverInfo.ServerModulesCount}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Количество API</td> |
||||
<td>{serverInfo.ServerAPICount}</td> |
||||
</tr> |
||||
</tbody> |
||||
</HTMLTable> |
||||
|
||||
const settings = |
||||
<HTMLTable> |
||||
<thead> |
||||
<tr> |
||||
<th>Параметр</th> |
||||
<th>Значение</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr> |
||||
<td>Адрес сервера</td> |
||||
<td><Link>{instanceSettings.ServerPrefix}</Link></td> |
||||
</tr> |
||||
<tr> |
||||
<td>Директория клиента</td> |
||||
<td>{instanceSettings.ServerUpdateClientFolderName}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Маска поиска модулей</td> |
||||
<td>{instanceSettings.ServerAPIPrefix}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Сокращенное имя сервера</td> |
||||
<td>{instanceSettings.ServerNameShort}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Полное имя сервера</td> |
||||
<td>{instanceSettings.ServerNameFull}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Тип БД</td> |
||||
<td>{instanceSettings.DBType}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Строка подключения к БД</td> |
||||
<td>{instanceSettings.DBConnectionString}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Схема БД</td> |
||||
<td>{instanceSettings.DBSchema}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Сжатие</td> |
||||
<td><Checkbox disabled={true} checked={instanceSettings.UsingCompression}/></td> |
||||
</tr> |
||||
<tr> |
||||
<td>CORS</td> |
||||
<td><Checkbox disabled={true} checked={instanceSettings.AllowCors}/></td> |
||||
</tr> |
||||
</tbody> |
||||
</HTMLTable> |
||||
|
||||
return (<> |
||||
<H1>О сервере</H1> |
||||
<Tabs id="Tabs"> |
||||
<Tab id="status" title="Статус" panel={status} /> |
||||
<Tab id="settings" title="Настройки" panel={settings} /> |
||||
</Tabs></> |
||||
); |
||||
}); |
||||
|
||||
export default AboutServer; |
@ -0,0 +1,109 @@
|
||||
import React, { useEffect } from 'react'; |
||||
import { H1, HTMLTable, Tab, Tabs } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import moment from 'moment'; |
||||
import { Link } from 'react-router-dom'; |
||||
import CallsStore from '../stores/CallsStore'; |
||||
import ObjectsStore from '../stores/ObjectsStore'; |
||||
|
||||
const Calls = observer(props => { |
||||
let { getAllCalls, objects } = CallsStore; |
||||
let { getTypes, getTypeByID, types } = ObjectsStore; |
||||
|
||||
useEffect(() => { |
||||
getTypes(); |
||||
objects.clear(); |
||||
getAllCalls(); |
||||
}, []) |
||||
|
||||
const typeImage = (id) => { |
||||
if (types.length) { |
||||
let imgString = getTypeByID(id).IMG_NORMAL; |
||||
return <img src={`data:image/png;base64,${imgString}`} /> |
||||
} |
||||
} |
||||
|
||||
const calls = <> |
||||
<HTMLTable interactive={true}> |
||||
<thead> |
||||
<th></th> |
||||
<th>Дата</th> |
||||
<th>Контактное лицо</th> |
||||
<th>Телефон</th> |
||||
</thead> |
||||
<tbody> |
||||
{objects.filter(o => o.ID_TYPE === 1006).map(o => { |
||||
return <tr> |
||||
<td>{typeImage(o.ID_TYPE)}</td> |
||||
<td style={{ minWidth: 150 }}>{moment(o.DateOfCreation).format('DD.MM.YY HH:mm:ss')}</td> |
||||
<td>{o.Pars['Контактное лицо']}</td> |
||||
<td><a href={`tel:${o.Pars['Телефон']}`}>{o.Pars['Телефон']}</a></td> |
||||
</tr> |
||||
})} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
const questions = <> |
||||
<HTMLTable interactive={true}> |
||||
<thead> |
||||
<th></th> |
||||
<th>Дата</th> |
||||
<th>Вопрос</th> |
||||
<th>Телефон</th> |
||||
<th>Эл. почта</th> |
||||
</thead> |
||||
<tbody> |
||||
{objects.filter(o => o.ID_TYPE === 1005).map(o => { |
||||
return <tr> |
||||
<td>{typeImage(o.ID_TYPE)}</td> |
||||
<td style={{ minWidth: 150 }}>{moment(o.DateOfCreation).format('DD.MM.YY HH:mm:ss')}</td> |
||||
<td>{o.Pars['Комментарий заказчика']}</td> |
||||
<td><a href={`tel:${o.Pars['Телефон']}`}>{o.Pars['Телефон']}</a></td> |
||||
<td><a href={`mailto:${o.Pars['Эл. почта']}`}>{o.Pars['Эл. почта']}</a></td> |
||||
</tr> |
||||
})} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
const requests = <> |
||||
<HTMLTable interactive={true}> |
||||
<thead> |
||||
<th></th> |
||||
<th>Дата</th> |
||||
<th>Услуга</th> |
||||
<th>Компания</th> |
||||
<th>Контактное лицо</th> |
||||
<th>Телефон</th> |
||||
<th>Эл. почта</th> |
||||
<th>Комментарий</th> |
||||
</thead> |
||||
<tbody> |
||||
{objects.filter(o => o.ID_TYPE === 1004).map(o => { |
||||
return <tr> |
||||
<td>{typeImage(o.ID_TYPE)}</td> |
||||
<td style={{ minWidth: 150 }}>{moment(o.DateOfCreation).format('DD.MM.YY HH:mm:ss')}</td> |
||||
<td>{o.Pars['Услуга']}</td> |
||||
<td>{o.Pars['Заказчик']}</td> |
||||
<td>{o.Pars['Контактное лицо']}</td> |
||||
<td><a href={`tel:${o.Pars['Телефон']}`}>{o.Pars['Телефон']}</a></td> |
||||
<td><a href={`mailto:${o.Pars['Эл. почта']}`}>{o.Pars['Эл. почта']}</a></td> |
||||
<td>{o.Pars['Комментарий заказчика']}</td> |
||||
</tr> |
||||
})} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
return (<> |
||||
<H1>Звонки и заявки</H1> |
||||
<Tabs id="Tabs"> |
||||
<Tab id="calls" title="Звонки" panel={calls} /> |
||||
<Tab id="questions" title="Вопросы" panel={questions} /> |
||||
<Tab id="requests" title="Заявки" panel={requests} /> |
||||
</Tabs></> |
||||
); |
||||
}); |
||||
|
||||
export default Calls; |
@ -0,0 +1,44 @@
|
||||
import React, { useEffect } from 'react'; |
||||
import { H1, Icon, HTMLTable } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import ObjectsStore from '../stores/ObjectsStore'; |
||||
|
||||
const Categories = observer(props => { |
||||
let { categories, getCategories } = ObjectsStore; |
||||
|
||||
useEffect(() => { |
||||
getCategories(); |
||||
}, []) |
||||
|
||||
const catsList = <> |
||||
<HTMLTable interactive={true}> |
||||
<thead> |
||||
<th>ID</th> |
||||
<th>Имя</th> |
||||
<th>Короткое имя</th> |
||||
<th>Таблица</th> |
||||
<th>Действия</th> |
||||
</thead> |
||||
<tbody> |
||||
{categories.map(c => { |
||||
return <tr> |
||||
<td>{c.ID_CAT}</td> |
||||
<td>{c.NAME}</td> |
||||
<td>{c.NAME_SHORT}</td> |
||||
<td>{c.DB_TBL_NAME}</td> |
||||
<td></td> |
||||
</tr> |
||||
})} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
|
||||
return (<> |
||||
<H1>Категории</H1> |
||||
{catsList} |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default Categories; |
@ -0,0 +1,14 @@
|
||||
import React from 'react'; |
||||
import { observer } from 'mobx-react'; |
||||
import config from '../config'; |
||||
|
||||
const Dashboard = observer(props => { |
||||
|
||||
return <> |
||||
<div > |
||||
<p>{config.api}</p> |
||||
</div> |
||||
</> |
||||
}); |
||||
|
||||
export default Dashboard; |
@ -0,0 +1,31 @@
|
||||
import React from 'react'; |
||||
import { Link } from 'react-router-dom'; |
||||
import { useHistory, useLocation } from 'react-router-dom'; |
||||
|
||||
//import './ErrorPage.css';
|
||||
|
||||
function ErrorPage(props) { |
||||
|
||||
let history = useHistory(); |
||||
|
||||
function goBack(e) { |
||||
e.preventDefault(); |
||||
history.goBack(); |
||||
} |
||||
|
||||
function goMain(e) { |
||||
e.preventDefault(); |
||||
history.push('/'); |
||||
} |
||||
|
||||
return ( |
||||
<div class="d-flex align-items-center flex-column justify-content-center h-100 bg-dark text-white" id="header"> |
||||
<h1 className="error-text">Ошибка 404</h1> |
||||
<p className="error-text"> |
||||
<Link onClick={goBack} className="error-link">← Назад</Link> · <Link onClick={goMain} className="error-link">На главную</Link> |
||||
</p> |
||||
</div> |
||||
) |
||||
} |
||||
|
||||
export default ErrorPage; |
@ -0,0 +1,29 @@
|
||||
import React, { useEffect, useState } from 'react'; |
||||
import { observer } from 'mobx-react'; |
||||
import { apiRequest } from '../deepApi'; |
||||
import Login from '../stores/LoginStore'; |
||||
|
||||
const Example = observer(props => { |
||||
const { session } = Login; |
||||
let [serverName, setServerName] = useState(''); |
||||
|
||||
useEffect(() => { |
||||
// Пример вызова апи
|
||||
// apiRequest(req, "api-...", session, res => ...);
|
||||
apiRequest(null, "sys-status", session, res => setServerName(res.ServerNameFull)); |
||||
|
||||
/* Еще пример |
||||
apiRequest(req, // тело запроса
|
||||
"api-...", // имя АПИ
|
||||
session, // сессия (в следующих версиях будет подставлена автоматически)
|
||||
res => ..., // результат
|
||||
err => ..., // сообщение об ошибки
|
||||
() => ..., // завершение
|
||||
null, |
||||
p => ...); // прогресс */
|
||||
}, []) |
||||
|
||||
return (<p>Example component, Server name: {serverName}</p>); |
||||
}); |
||||
|
||||
export default Example; |
@ -0,0 +1,115 @@
|
||||
import React, { useState, useEffect } from 'react'; |
||||
import { H1, HTMLTable, Card, InputGroup, Button, ControlGroup, Callout, Intent, FormGroup, ButtonGroup, Icon } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import ServersStore from '../stores/ServersStore'; |
||||
import { apiUnAuthCustomRequest } from '../deepApi' |
||||
import moment from 'moment'; |
||||
|
||||
const FavServers = observer(props => { |
||||
|
||||
let [currentServer, setCurrentServer] = useState(''); |
||||
let [currentServerOk, setCurrentServerOk] = useState(null); |
||||
let [calloutText, setCalloutText] = useState(''); |
||||
|
||||
let { servers, getServers, addServer, clearServers, checkServer, checkServers, removeServer } = ServersStore; |
||||
|
||||
function showStatus(server) { |
||||
if (server.status) { |
||||
return <><Icon icon="tick" intent={Intent.SUCCESS} /><span> {server.result}</span></> |
||||
} else if (server.status === undefined) { |
||||
return <span>--</span> |
||||
} else { |
||||
return <><Icon icon="cross" intent={Intent.DANGER} /><span> {server.result}</span></> |
||||
} |
||||
} |
||||
|
||||
useEffect(() => { |
||||
getServers(); |
||||
}, []); |
||||
|
||||
const serversList = <> |
||||
<div><HTMLTable interactive={true}> |
||||
<thead> |
||||
<th>№</th> |
||||
<th>Сервер</th> |
||||
<th>Действия</th> |
||||
<th>Ответ</th> |
||||
<th>Последняя проверка</th> |
||||
</thead> |
||||
<tbody> |
||||
{servers.map((s, index) => { |
||||
return <tr> |
||||
<td key={index}>{index + 1}</td> |
||||
<td>{s.server}</td> |
||||
<td><ButtonGroup minimal={true}> |
||||
<Button icon='remove' onClick={() => removeServer(s.server)} /> |
||||
<Button icon='diagnosis' onClick={() => checkServer(s.server)} /> |
||||
</ButtonGroup></td> |
||||
<td>{showStatus(s)}</td> |
||||
<td>{moment(s.date).format('DD.MM.YY HH:mm:ss')}</td> |
||||
</tr> |
||||
})} |
||||
</tbody> |
||||
</HTMLTable></div> |
||||
</> |
||||
|
||||
function isValidHttpUrl(string) { |
||||
let url; |
||||
try { |
||||
url = new URL(string); |
||||
} catch (_) { |
||||
return false; |
||||
} |
||||
|
||||
return url.protocol === "http:" || url.protocol === "https:"; |
||||
} |
||||
|
||||
let serverFailed = <FormGroup> |
||||
<Callout intent={Intent.WARNING}>Сервер <a href={currentServer}>{currentServer}</a> недоступен ({calloutText})</Callout> |
||||
</FormGroup> |
||||
|
||||
let serverOk = <FormGroup> |
||||
<Callout intent={Intent.SUCCESS}>{calloutText}</Callout> |
||||
</FormGroup> |
||||
|
||||
function checkCurrentServer(server) { |
||||
apiUnAuthCustomRequest(null, "sys-status", server, (res) => { |
||||
setCurrentServerOk(true); setCalloutText(res.ServerNameFull) |
||||
}, (err) => { setCurrentServerOk(false); setCalloutText(err) }); |
||||
} |
||||
|
||||
const addServerForm = <> |
||||
<Card> |
||||
<h5>Добавить</h5> |
||||
<FormGroup> |
||||
<ControlGroup> |
||||
<InputGroup |
||||
placeholder="http://server:port" |
||||
value={currentServer} |
||||
onChange={(e) => { setCurrentServer(e.target.value); setCurrentServerOk(null); }} |
||||
/> |
||||
<Button text="Тест" icon='diagnosis' disabled={!(!!currentServer)} onClick={() => checkCurrentServer(currentServer)} /> |
||||
<Button text="Добавить" icon='add' disabled={!(!!currentServer && !!isValidHttpUrl(currentServer))} |
||||
onClick={() => { addServer(currentServer); setCurrentServer(''); }} /> |
||||
</ControlGroup> |
||||
</FormGroup> |
||||
{currentServerOk && serverOk} |
||||
{currentServerOk === false && serverFailed} |
||||
<h5>Действия</h5> |
||||
<ButtonGroup> |
||||
<Button icon="diagnosis" disabled={(servers.length === 0)} onClick={checkServers}>Проверить все</Button> |
||||
<Button icon="cross" intent={Intent.DANGER} disabled={(servers.length === 0)} onClick={clearServers}>Очистить список</Button> |
||||
</ButtonGroup> |
||||
</Card> |
||||
</> |
||||
|
||||
return (<> |
||||
<H1>Избранные сервера</H1> |
||||
<p>Эти настройки сохраняются для всех пользователей компьютера</p> |
||||
{serversList} |
||||
{addServerForm} |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default FavServers; |
@ -0,0 +1,83 @@
|
||||
import React, { useEffect } from 'react'; |
||||
import { H1, H5, HTMLTable, Tab, Tabs, Tag, NonIdealState } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import Users from '../stores/UsersStore'; |
||||
import { NavLink } from 'react-router-dom'; |
||||
import LocalStore from '../stores/LocalStore'; |
||||
|
||||
const LeftMenu = observer(props => { |
||||
|
||||
const { users, getUsers } = Users; |
||||
let { darkTheme } = LocalStore; |
||||
|
||||
useEffect(() => { |
||||
getUsers(); |
||||
}, []) |
||||
|
||||
const menuStyle = { |
||||
padding: 20, |
||||
background: darkTheme ? '#30404D' : '#EBF1F5', |
||||
display: 'flex', |
||||
maxWidth: 200 |
||||
} |
||||
|
||||
const listStyle = { |
||||
listStyleType: 'none', |
||||
padding: 0, |
||||
lineHeight: 1.4, |
||||
} |
||||
|
||||
const liStyle = { |
||||
marginBottom: 4 |
||||
} |
||||
|
||||
const sectionStyle = { |
||||
color: '#5C7080', |
||||
fontVariant: 'all-small-caps', |
||||
} |
||||
|
||||
const activeStyle = { |
||||
fontWeight: "bold", |
||||
color: darkTheme ? '#FFFFFF' : "#30404D", |
||||
}; |
||||
|
||||
const menu = [ |
||||
{ |
||||
section: "Администрация", |
||||
links: [ |
||||
{ link: "/users", caption: "Пользователи" }, |
||||
{ link: "/info", caption: "О сервере" }, |
||||
{ link: "/logs", caption: "Логи" }, |
||||
] |
||||
}, |
||||
{ |
||||
section: "Объекты", |
||||
links: [ |
||||
{ link: "/categories", caption: "Категории" }, |
||||
{ link: "/types", caption: "Типы объектов" }, |
||||
{ link: "/objects", caption: "Объекты" }, |
||||
] |
||||
}, |
||||
{ |
||||
section: "Всякая фигня", |
||||
links: [ |
||||
{ link: "/servers", caption: "Избранные сервера" }, |
||||
{ link: "/permstest", caption: "Тест разрешений (modDynext)" }, |
||||
{ link: "/permstest2", caption: "Тест разрешений (NO_SUCH_PERM)" }, |
||||
{ link: "/calls", caption: "Звонки" }, |
||||
] |
||||
}, |
||||
] |
||||
|
||||
return (<> |
||||
<div style={menuStyle}> |
||||
<nav> |
||||
{menu.map(m => <><H5 style={sectionStyle}>{m.section}</H5><ul style={listStyle}> |
||||
{m.links.map(l => <li style={liStyle}><NavLink activeStyle={activeStyle} to={l.link}>{l.caption}</NavLink></li>)}</ul></>)} |
||||
</nav> |
||||
</div> |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default LeftMenu; |
@ -0,0 +1,230 @@
|
||||
import React, { useState, useEffect } from 'react'; |
||||
import { Button, Tooltip, InputGroup, FormGroup, Intent, Callout, Card, Switch, ControlGroup, HTMLSelect, MenuItem, H2 } from '@blueprintjs/core'; |
||||
import { Select } from "@blueprintjs/select"; |
||||
import { observer } from 'mobx-react'; |
||||
import LoginStore from '../stores/LoginStore'; |
||||
import { useHistory, useLocation } from 'react-router-dom'; |
||||
import { apiUnAuthCustomRequest, changeServer, DEEP_SERVER } from '../deepApi'; |
||||
import ServersStore from '../stores/ServersStore'; |
||||
import config from '../config'; |
||||
import Info from '../stores/ServerInfo'; |
||||
|
||||
const Login = observer(props => { |
||||
let [showPassword, setShowPassword] = useState(false); |
||||
let [username, setUsername] = useState(''); |
||||
let [password, setPassword] = useState(""); |
||||
let [customServer, setCustomServer] = useState(null); |
||||
let [useCustomServer, setUseCustomServer] = useState(false); |
||||
let [customServerOk, setCustomServerOk] = useState(null); |
||||
let [customServerProtocol, setCustomServerProtocol] = useState('http'); |
||||
let [error, setError] = useState(null); |
||||
let [hasLogins, setHasLogins] = useState(null); |
||||
|
||||
let { servers, getServers, addServer } = ServersStore; |
||||
|
||||
let [calloutText, setCalloutText] = useState(null); |
||||
|
||||
let { login, isLoggedIn, incorrectPass } = LoginStore; |
||||
|
||||
let history = useHistory(); |
||||
let location = useLocation(); |
||||
|
||||
let { from } = location.state || { from: { pathname: "/" } }; |
||||
const { serverStatus, getData } = Info; |
||||
|
||||
const cardStyle = { |
||||
width: 500, |
||||
alignSelf: 'center', |
||||
margin: 'auto' |
||||
} |
||||
|
||||
let insecureServerError = config.allowMultiLogin && useCustomServer |
||||
&& customServerProtocol === 'http' && window.location.protocol === 'https:'; |
||||
|
||||
|
||||
useEffect(() => { |
||||
if (isLoggedIn) { |
||||
addServer(DEEP_SERVER, undefined, undefined); |
||||
history.replace(from); |
||||
} |
||||
if (serverStatus === false) { |
||||
setError('Сервер не отвечает') |
||||
} |
||||
if (incorrectPass && hasLogins) { |
||||
setError('Неправильный логин или пароль'); |
||||
} |
||||
}); |
||||
|
||||
useEffect(() => { |
||||
if (from.pathname !== '/') { |
||||
setError('Чтобы просматривать эту страницу, войдите на сайт'); |
||||
} |
||||
getServers(); |
||||
}, []); |
||||
|
||||
const lockButton = ( |
||||
<Tooltip content={`${showPassword ? "Скрыть" : "Показать"} пароль`}> |
||||
<Button |
||||
icon={showPassword ? "unlock" : "lock"} |
||||
intent={Intent.WARNING} |
||||
minimal={true} |
||||
onClick={() => setShowPassword(!showPassword)} |
||||
/> |
||||
</Tooltip> |
||||
); |
||||
|
||||
let alert = (text) => { |
||||
return <FormGroup> |
||||
<Callout intent={Intent.WARNING}>{text}</Callout> |
||||
</FormGroup> |
||||
} |
||||
|
||||
let doLogin = () => { |
||||
setHasLogins(true); |
||||
if (useCustomServer) { |
||||
apiUnAuthCustomRequest(null, "sys-status", `${customServerProtocol}://${customServer}`, (res) => { |
||||
changeServer(`${customServerProtocol}://${customServer}`); |
||||
login(username, password); |
||||
}, (err) => { |
||||
setError(err); |
||||
handleCustomServerState(false); |
||||
}); |
||||
|
||||
} else { |
||||
login(username, password); |
||||
} |
||||
} |
||||
|
||||
function handleSwitch() { |
||||
setUseCustomServer(!useCustomServer); |
||||
handleCustomServerState(); |
||||
} |
||||
|
||||
let options = ["HTTP", "HTTPS"]; |
||||
let customServerForm = <> |
||||
<FormGroup> |
||||
<Switch label="Другой сервер" defaultChecked={useCustomServer} |
||||
onChange={handleSwitch} /> |
||||
<ControlGroup> |
||||
<HTMLSelect options={options} disabled={!useCustomServer} value={customServerProtocol.toUpperCase()} onChange={(e) => { setCustomServerProtocol(e.target.value.toLowerCase()); handleCustomServerState(null); }} /> |
||||
<InputGroup disabled={!useCustomServer} |
||||
placeholder="server:port" |
||||
value={customServer} |
||||
onChange={(e) => { setCustomServer(e.target.value); handleCustomServerState(null); }} |
||||
/> |
||||
<Button text="Тест" disabled={!useCustomServer || !(!!customServer) && !insecureServerError} |
||||
onClick={() => { customServerTest(); handleCustomServerState(null); }} /> |
||||
</ControlGroup> |
||||
</FormGroup> |
||||
<FormGroup> |
||||
<Select |
||||
items={servers} |
||||
filterable={false} |
||||
disabled={!useCustomServer} |
||||
noResults={<MenuItem disabled={true} text="Нет сохраненных адресов" />} |
||||
itemRenderer={itemRenderer} |
||||
onItemSelect={handleClick}> |
||||
<Button text={'Сохраненные адреса'} rightIcon="caret-down" /> |
||||
</Select> |
||||
</FormGroup> |
||||
</> |
||||
|
||||
function itemRenderer(item, { handleClick }) { |
||||
return ( |
||||
<MenuItem |
||||
key={item.server} |
||||
text={item.server} |
||||
onClick={handleClick} |
||||
shouldDismissPopover={true} |
||||
/> |
||||
) |
||||
} |
||||
|
||||
function handleClick(item) { |
||||
var url = new URL(item.server); |
||||
setCustomServerProtocol(url.protocol.replace(":", "").toUpperCase()); |
||||
setCustomServer(url.host); |
||||
setCustomServerOk(null); |
||||
} |
||||
|
||||
let title = <span>Сервер <a href={`${customServerProtocol}://${customServer}`}>{`${customServerProtocol}://${customServer}`}</a> недоступен</span> |
||||
let serverFailed = <FormGroup> |
||||
<Callout intent={Intent.WARNING} title={title}>{calloutText}</Callout> |
||||
</FormGroup> |
||||
|
||||
let serverOk = <FormGroup> |
||||
<Callout intent={Intent.SUCCESS}>{calloutText}</Callout> |
||||
</FormGroup> |
||||
|
||||
let insecureServer = <FormGroup> |
||||
<Callout intent={Intent.DANGER}>Доступ к серверу по HTTP отсюда невозможен</Callout> |
||||
</FormGroup> |
||||
|
||||
function customServerTest() { |
||||
apiUnAuthCustomRequest(null, "sys-status", `${customServerProtocol}://${customServer}`, (res) => { handleResponse(res) }, (err) => { handleError(err) }); |
||||
} |
||||
|
||||
function handleResponse(response) { |
||||
setCalloutText(response.ServerNameFull); |
||||
handleCustomServerState(true); |
||||
} |
||||
|
||||
function handleError(err) { |
||||
setCalloutText(err); |
||||
handleCustomServerState(false); |
||||
} |
||||
|
||||
function handleCustomServerState(state) { |
||||
setCustomServerOk(state); |
||||
} |
||||
|
||||
function loginButtonState() { |
||||
if (useCustomServer) { |
||||
return !(!!username && !!password && !!customServer); |
||||
} else { |
||||
return !(!!username && !!password) || !serverStatus; |
||||
} |
||||
} |
||||
|
||||
function loginButtonText() { |
||||
if (useCustomServer && DEEP_SERVER !== `${customServerProtocol}://${customServer}`) { |
||||
return "Сменить сервер и войти"; |
||||
} else { |
||||
return "Войти"; |
||||
} |
||||
} |
||||
|
||||
return ( |
||||
<> |
||||
<Card style={cardStyle}> |
||||
<H2>Вход в систему</H2> |
||||
{!!error && alert(error)} |
||||
<FormGroup> |
||||
<InputGroup |
||||
placeholder="Логин" |
||||
onChange={(e) => setUsername(e.target.value)} |
||||
/> |
||||
</FormGroup> |
||||
<FormGroup> |
||||
<InputGroup |
||||
placeholder="Пароль" |
||||
type={showPassword ? "text" : "password"} |
||||
rightElement={lockButton} |
||||
onChange={(e) => setPassword(e.target.value)} |
||||
/> |
||||
</FormGroup> |
||||
{config.allowMultiLogin && props.customServer && customServerForm} |
||||
{insecureServerError && insecureServer} |
||||
{useCustomServer && customServerOk && serverOk} |
||||
{useCustomServer && customServerOk === false && serverFailed} |
||||
<FormGroup> |
||||
<Button text={loginButtonText()} intent="primary" onClick={doLogin} |
||||
disabled={loginButtonState()} /> |
||||
</FormGroup> |
||||
|
||||
</Card> |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default Login; |
@ -0,0 +1,86 @@
|
||||
import React, { useEffect, useState } from 'react'; |
||||
import { H1, Spinner, HTMLTable, ControlGroup, InputGroup, Button, FormGroup, NonIdealState } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import ObjectsStore from '../stores/ObjectsStore'; |
||||
import moment from 'moment'; |
||||
|
||||
const Object = observer(props => { |
||||
let { getTypes, getAllObjects, objects, getObjects, getTypeByID, types, fetching, getObject } = ObjectsStore; |
||||
let [searchMask, setSearchMask] = useState(''); |
||||
let [waitingForFetch, setWaitingForFetch] = useState(false) |
||||
|
||||
useEffect(() => { |
||||
getTypes(); |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
if (searchMask.length > 2) { |
||||
setWaitingForFetch(true); |
||||
getObjects(searchMask); |
||||
setWaitingForFetch(fetching); |
||||
} |
||||
}, [searchMask]) |
||||
|
||||
|
||||
const typeImage = (id) => { |
||||
if (types.length) { |
||||
let imgString = getTypeByID(id).IMG_NORMAL; |
||||
return <img src={`data:image/png;base64,${imgString}`} /> |
||||
} |
||||
} |
||||
|
||||
const objsList = <> |
||||
<HTMLTable interactive={true}> |
||||
<thead> |
||||
<th></th> |
||||
<th>ID</th> |
||||
<th>ID_CAT</th> |
||||
<th>ID_TYPE</th> |
||||
<th>Обозначение</th> |
||||
<th>Наименование</th> |
||||
<th>Дата создания</th> |
||||
<th>Дата изменения</th> |
||||
</thead> |
||||
<tbody> |
||||
{objects.map(o => { |
||||
return <tr> |
||||
<td>{typeImage(o.ID_TYPE)}</td> |
||||
<td>{o.ID}</td> |
||||
<td>{o.ID_CAT}</td> |
||||
<td>{o.ID_TYPE}</td> |
||||
<td><code>{o.SPEC}</code></td> |
||||
<td>{o.NAME}</td> |
||||
<td style={{ minWidth: 150 }}>{moment(o.DateOfCreation).format('DD.MM.YY HH:mm:ss')}</td> |
||||
<td style={{ minWidth: 150 }}>{moment(o.DateOfModification).format('DD.MM.YY HH:mm:ss')}</td> |
||||
</tr> |
||||
})} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
let clearButton = <Button icon="cross" disabled={!searchMask} minimal={true} onClick={() => setSearchMask('')} /> |
||||
|
||||
let noResults = <NonIdealState |
||||
icon={'search'} |
||||
title={searchMask.length > 2 ? `Нет результатов по запросу "${searchMask}"` : 'Введите запрос'} |
||||
/> |
||||
|
||||
return (<> |
||||
<H1>Объекты</H1> |
||||
<FormGroup label={'Фильтр'} labelInfo="(минимум 3 символа)"> |
||||
<ControlGroup> |
||||
<InputGroup style={{ width: 500 }} |
||||
value={searchMask} |
||||
placeholder="Обозначение или наименование" |
||||
rightElement={clearButton} |
||||
onChange={(e) => setSearchMask(e.target.value)} /> |
||||
{fetching && <div style={{ marginLeft: 10, display: 'flex', alignItems: 'center' }} ><Spinner size={Spinner.SIZE_SMALL} /></div>} |
||||
|
||||
</ControlGroup> |
||||
</FormGroup> |
||||
{(objects.length === 0 || searchMask === '' && !fetching) ? noResults : objsList} |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default Object; |
@ -0,0 +1,54 @@
|
||||
import React, { useEffect } from 'react'; |
||||
import { H1, HTMLTable, Tooltip, Classes } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import ObjectsStore from '../stores/ObjectsStore'; |
||||
|
||||
const ObjectTypes = observer(props => { |
||||
let { types, getTypes } = ObjectsStore; |
||||
|
||||
useEffect(() => { |
||||
getTypes(); |
||||
}, []) |
||||
|
||||
const isCyr = (term) => /[а-яА-ЯЁё]/.test(term); |
||||
const isEng = (term) => /[a-zA-Z]/.test(term); |
||||
|
||||
const toolTip = (t) => |
||||
<Tooltip className={Classes.TOOLTIP_INDICATOR} |
||||
content={`Буква: ${t.CHARACTER} ${isCyr(t.CHARACTER) ? '🇷🇺' : isEng(t.CHARACTER) ? '🇬🇧' : null}`} > |
||||
{t.ID_TYPE} |
||||
</Tooltip> |
||||
|
||||
const typesList = <> |
||||
<HTMLTable interactive={true}> |
||||
<thead> |
||||
<th></th> |
||||
<th>ID</th> |
||||
<th>Группа</th> |
||||
<th>Имя</th> |
||||
<th>Описание</th> |
||||
</thead> |
||||
<tbody> |
||||
{types.map(t => { |
||||
return <tr> |
||||
<td>{<img src={`data:image/png;base64,${t.IMG_NORMAL}`} />}</td> |
||||
<td>{toolTip(t)}</td> |
||||
<td>{t.GROUP_NAME}</td> |
||||
<td>{t.NAME}</td> |
||||
<td>{t.DESCRIPTION}</td> |
||||
<td></td> |
||||
</tr> |
||||
})} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
|
||||
return (<> |
||||
<H1>Типы объектов</H1> |
||||
{typesList} |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default ObjectTypes; |
@ -0,0 +1,217 @@
|
||||
import React, { useEffect, useState } from 'react'; |
||||
import { H1, Spinner, HTMLTable, ControlGroup, InputGroup, Button, FormGroup, NonIdealState, MenuItem, Callout, Intent } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import ObjectsStore from '../stores/ObjectsStore'; |
||||
import moment from 'moment'; |
||||
import { useHistory, useParams } from 'react-router-dom'; |
||||
import { MultiSelect } from "@blueprintjs/select"; |
||||
import { observable, toJS } from 'mobx'; |
||||
|
||||
const Objects = observer(props => { |
||||
let { catID } = useParams(); |
||||
|
||||
let { getTypes, objects, getObjects, getTypeByID, types, fetching, getCategories, categories } = ObjectsStore; |
||||
let [searchMask, setSearchMask] = useState(''); |
||||
let history = useHistory(); |
||||
|
||||
let selectedCats = observable([]); |
||||
|
||||
useEffect(() => { |
||||
getTypes(); |
||||
getCategories(); |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
if (searchMask.length > 2) { |
||||
console.log("rerender"); |
||||
|
||||
selectedCats.map(c => console.log(c.ID_CAT)) |
||||
console.log((selectedCats)) |
||||
|
||||
console.log(JSON.stringify(selectedCats)) |
||||
getObjects(searchMask, selectedCats.map(c => console.log(c.ID_CAT))); |
||||
} |
||||
}, [searchMask]) |
||||
|
||||
useEffect(() => { |
||||
if (catID) { |
||||
let catByID = categories.find(c => String(c.ID_CAT) === catID); |
||||
if (catByID) { |
||||
selectedCats.clear(); |
||||
selectedCats.push(catByID); |
||||
} else { |
||||
categories.map(c => selectedCats.push(c)); |
||||
} |
||||
} else { |
||||
categories.map(c => selectedCats.push(c)); |
||||
} |
||||
}, [selectedCats, searchMask]) |
||||
|
||||
|
||||
const typeImage = (id) => { |
||||
if (types.length) { |
||||
let imgString = getTypeByID(id).IMG_NORMAL; |
||||
return <img src={`data:image/png;base64,${imgString}`} /> |
||||
} |
||||
} |
||||
|
||||
const objsList = <> |
||||
<HTMLTable interactive={true}> |
||||
<thead> |
||||
<th></th> |
||||
<th>ID</th> |
||||
<th>ID_CAT</th> |
||||
<th>ID_TYPE</th> |
||||
<th>Обозначение</th> |
||||
<th>Наименование</th> |
||||
<th>Дата создания</th> |
||||
<th>Дата изменения</th> |
||||
</thead> |
||||
<tbody> |
||||
{objects.map(o => { |
||||
return <tr onClick={(e) => history.push(`/logs/${o.ID_CAT}/${o.ID}`)}> |
||||
<td>{typeImage(o.ID_TYPE)}</td> |
||||
<td>{o.ID}</td> |
||||
<td>{o.ID_CAT}</td> |
||||
<td>{o.ID_TYPE}</td> |
||||
<td><code>{o.SPEC}</code></td> |
||||
<td>{o.NAME}</td> |
||||
<td style={{ minWidth: 150 }}>{moment(o.DateOfCreation).format('DD.MM.YY HH:mm:ss')}</td> |
||||
<td style={{ minWidth: 150 }}>{moment(o.DateOfModification).format('DD.MM.YY HH:mm:ss')}</td> |
||||
</tr> |
||||
})} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
let clearButton = <Button icon="cross" disabled={!searchMask} minimal={true} onClick={() => setSearchMask('')} /> |
||||
|
||||
let noResults = <NonIdealState |
||||
icon={'search'} |
||||
title={searchMask.length > 2 ? `Нет результатов по запросу "${searchMask}"` : 'Введите запрос'} |
||||
/> |
||||
|
||||
function highlightText(text, query) { |
||||
let lastIndex = 0; |
||||
const words = query |
||||
.split(/\s+/) |
||||
.filter(word => word.length > 0) |
||||
.map(escapeRegExpChars); |
||||
if (words.length === 0) { |
||||
return [text]; |
||||
} |
||||
const regexp = new RegExp(words.join("|"), "gi"); |
||||
const tokens = []; |
||||
while (true) { |
||||
const match = regexp.exec(text); |
||||
if (!match) { |
||||
break; |
||||
} |
||||
const length = match[0].length; |
||||
const before = text.slice(lastIndex, regexp.lastIndex - length); |
||||
if (before.length > 0) { |
||||
tokens.push(before); |
||||
} |
||||
lastIndex = regexp.lastIndex; |
||||
tokens.push(<strong key={lastIndex}>{match[0]}</strong>); |
||||
} |
||||
const rest = text.slice(lastIndex); |
||||
if (rest.length > 0) { |
||||
tokens.push(rest); |
||||
} |
||||
return tokens; |
||||
} |
||||
|
||||
function escapeRegExpChars(text) { |
||||
return text.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); |
||||
} |
||||
|
||||
function itemRenderer(item, { modifiers, handleClick, query }) { |
||||
return ( |
||||
<MenuItem |
||||
active={modifiers.active} |
||||
icon={selectedCats.includes(item) ? "tick" : "blank"} |
||||
key={item.NAME} |
||||
onClick={handleClick} |
||||
text={item.NAME} |
||||
label={item.ID_CAT} |
||||
shouldDismissPopover={false} |
||||
key={item.ID_CAT} |
||||
text={highlightText(item.NAME, query)} |
||||
/> |
||||
) |
||||
} |
||||
|
||||
function handleClick(item) { |
||||
/*var url = new URL(item.server); |
||||
setCustomServerProtocol(url.protocol.replace(":", "").toUpperCase()); |
||||
setCustomServer(url.host); |
||||
setCustomServerOk(null);*/ |
||||
} |
||||
|
||||
const renderTag = (item) => item.NAME; |
||||
|
||||
const handleCatSelect = (item) => { |
||||
if (selectedCats.includes(item)) { |
||||
selectedCats.remove(item); |
||||
} else { |
||||
selectedCats.push(item); |
||||
} |
||||
} |
||||
|
||||
const handleTagRemove = (tag, index) => { |
||||
selectedCats.remove(selectedCats.find(c => c.NAME === tag)); |
||||
}; |
||||
|
||||
const handleItemListPredicate = (query, items) => { |
||||
return items.filter(c => c.NAME.toLowerCase().includes(query.toLowerCase())); |
||||
} |
||||
|
||||
return (<> |
||||
<H1>Объекты</H1> |
||||
<FormGroup label={'Фильтр'} labelInfo="(минимум 3 символа)"> |
||||
<ControlGroup> |
||||
<InputGroup style={{ width: 500 }} |
||||
value={searchMask} |
||||
placeholder="Обозначение или наименование" |
||||
rightElement={clearButton} |
||||
onChange={(e) => setSearchMask(e.target.value)} /> |
||||
{fetching && <div style={{ marginLeft: 10, display: 'flex', alignItems: 'center' }} ><Spinner size={Spinner.SIZE_SMALL} /></div>} |
||||
|
||||
</ControlGroup> |
||||
</FormGroup> |
||||
<FormGroup label={'Категории (работает так себе)'}> |
||||
<MultiSelect |
||||
placeholder={'Выберите категории'} |
||||
itemRenderer={itemRenderer} |
||||
items={categories} |
||||
selectedItems={selectedCats} |
||||
noResults={<MenuItem disabled={true} text="Нет категорий" />} |
||||
onItemSelect={handleCatSelect} |
||||
onItemsPaste={() => { }} |
||||
tagRenderer={renderTag} |
||||
resetOnSelect={true} |
||||
// itemPredicate={handleItemPredicate}
|
||||
// onActiveItemChange={()=>{}}
|
||||
itemListPredicate={handleItemListPredicate} |
||||
fill={true} |
||||
openOnKeyDown={false} |
||||
popoverProps={{ minimal: true }} |
||||
tagInputProps={{ |
||||
onRemove: handleTagRemove, |
||||
tagProps: { |
||||
minimal: true |
||||
}, |
||||
}} |
||||
/> |
||||
</FormGroup> |
||||
<FormGroup> |
||||
{true && <Callout intent={Intent.DANGER}>Нет категории {catID}</Callout>} |
||||
</FormGroup> |
||||
<Button onClick={() => { selectedCats.clear() }}>test</Button> |
||||
{(objects.length === 0 || searchMask === '' && !fetching) ? noResults : objsList} |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default Objects; |
@ -0,0 +1,18 @@
|
||||
import React from 'react'; |
||||
import { H1, Icon, Intent } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import { useHistory } from 'react-router-dom'; |
||||
|
||||
const PermsTest = observer(props => { |
||||
const history = useHistory(); |
||||
|
||||
return (<> |
||||
<H1>Тест разрешений</H1> |
||||
<p>Для страницы <code>{history.location.pathname}</code> требуется разрешение <code>{props.permission}</code> |
||||
<Icon icon={'endorsed'} intent={Intent.SUCCESS} /> |
||||
</p> |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default PermsTest; |
@ -0,0 +1,51 @@
|
||||
import React, { useEffect } from 'react'; |
||||
import { Route, Redirect, useHistory, } from 'react-router-dom' |
||||
import { observer } from 'mobx-react'; |
||||
import LoginStore from '../stores/LoginStore'; |
||||
import { Button, Callout, Intent } from '@blueprintjs/core'; |
||||
|
||||
const PrivateRoute = observer(({ component: Component, permission, ...rest }) => { |
||||
let { isLoggedIn, hasPermission, getUserInfo, isAdmin } = LoginStore; |
||||
let history = useHistory(); |
||||
|
||||
useEffect(() => { |
||||
getUserInfo(); |
||||
}, []) |
||||
|
||||
const cardStyle = { |
||||
width: 400, |
||||
alignSelf: 'center', |
||||
margin: 'auto' |
||||
} |
||||
|
||||
let hasPerm = hasPermission(permission); |
||||
// TODO сделать спиннер
|
||||
let alert = <Callout style={cardStyle} intent={Intent.WARNING} > |
||||
<p>У вас нет разрешения на доступ к этой странице</p> |
||||
{isAdmin && <p>Требуется разрешение <code>{permission}</code></p>} |
||||
{<Button onClick={() => history.goBack()}>Назад</Button>} |
||||
</Callout > |
||||
|
||||
function f(props) { |
||||
if (isLoggedIn) { |
||||
if (!permission) { |
||||
return <Component permission={permission} {...props} />; |
||||
} else if (hasPerm) { |
||||
return <Component permission={permission} {...props} /> |
||||
} else { |
||||
return alert; |
||||
} |
||||
} else { |
||||
return <Redirect to={{ |
||||
pathname: '/login', |
||||
state: { from: props.location } |
||||
}} /> |
||||
} |
||||
} |
||||
|
||||
return <Route {...rest} render={(props) => f(props)} /> |
||||
}); |
||||
|
||||
export default PrivateRoute; |
||||
|
||||
|
@ -0,0 +1,67 @@
|
||||
import React, { useEffect } from 'react'; |
||||
import logo from '../logo.svg'; |
||||
//import '../App.css';
|
||||
import { Button, Navbar, NavbarGroup, NavbarHeading, NavbarDivider, Classes, Alignment, Menu, MenuItem, Popover, Position, HTMLTable, H1, H2 } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import Info from '../stores/ServerInfo'; |
||||
|
||||
const ServerInfo = observer(props => { |
||||
|
||||
const { counter, getData, serverInfo, instanceSettings, getInstanceSettings } = Info; |
||||
|
||||
useEffect(() => { |
||||
getInstanceSettings() |
||||
}); |
||||
|
||||
return (<> |
||||
<H1>О сервере</H1> |
||||
<H2>Статус</H2> |
||||
<HTMLTable> |
||||
<thead> |
||||
<tr> |
||||
<th>Параметр</th> |
||||
<th>Значение</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr> |
||||
<td>Blueprint</td> |
||||
<td>{serverInfo.ServerNameFull}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>TSLint</td> |
||||
<td>{serverInfo.ServerNameFull}</td> |
||||
</tr> |
||||
</tbody> |
||||
</HTMLTable> |
||||
<H2>Настройки</H2> |
||||
<HTMLTable> |
||||
<thead> |
||||
<tr> |
||||
<th>Параметр</th> |
||||
<th>Значение</th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr> |
||||
<td>Blueprint</td> |
||||
<td>{instanceSettings.ServerPrefix}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>TSLint</td> |
||||
<td>{serverInfo.ServerNameFull}</td> |
||||
</tr> |
||||
</tbody> |
||||
</HTMLTable> |
||||
|
||||
|
||||
<button onClick={getData}>inc</button> |
||||
<p>{serverInfo.ServerNameFull}</p> |
||||
<p>{counter}</p> |
||||
|
||||
<p></p> |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default ServerInfo; |
@ -0,0 +1,66 @@
|
||||
import React, { useEffect, useState } from 'react'; |
||||
import { TextArea, ControlGroup, FormGroup, Switch, ProgressBar, Breadcrumbs, Breadcrumb, Icon, Button } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import Login from '../stores/LoginStore'; |
||||
import { apiRequest } from '../deepApi'; |
||||
import { useParams } from 'react-router-dom'; |
||||
import ServerLogFancy from './ServerLogFancy'; |
||||
|
||||
const ServerLog = observer(props => { |
||||
|
||||
const [log, setLog] = useState(""); |
||||
const [showFancy, setShowFancy] = useState(false); |
||||
const [loading, setLoading] = useState(false); |
||||
let [percent, setPercent] = useState(0); |
||||
|
||||
const { session } = Login; |
||||
|
||||
let { filename } = useParams(); |
||||
|
||||
const BREADCRUMBS = [ |
||||
{ href: "/logs", icon: "folder-close", text: "Логи" }, |
||||
{ href: `/logs/${filename}`, icon: "document", text: filename }, |
||||
]; |
||||
|
||||
useEffect(() => { |
||||
getLog(); |
||||
}, []); |
||||
|
||||
function getLog() { |
||||
setLoading(true); |
||||
const req = { FileName: filename }; |
||||
apiRequest(req, "api-log-getfilecontent", session, (res) => { setLog(res.FileContent); setLoading(false); }, |
||||
null, null, null, (e) => { setPercent(e / 100) }); |
||||
} |
||||
|
||||
const renderCurrentBreadcrumb = ({ text, ...restProps }) => { |
||||
return <Breadcrumb {...restProps}>{text}</Breadcrumb>; |
||||
}; |
||||
|
||||
const progressCard = { |
||||
width: '500px', |
||||
alignSelf: 'center', |
||||
margin: 'auto' |
||||
} |
||||
|
||||
return (<> |
||||
<Breadcrumbs |
||||
currentBreadcrumbRenderer={renderCurrentBreadcrumb} |
||||
items={BREADCRUMBS} |
||||
/> |
||||
{loading ? |
||||
<div style={progressCard}> |
||||
<ProgressBar value={percent} animate={true}/> |
||||
</div> : <> |
||||
<FormGroup> |
||||
<ControlGroup> |
||||
<Switch checked={showFancy} label="Табличный вид" onChange={(_) => setShowFancy(!showFancy)} /> |
||||
</ControlGroup> |
||||
</FormGroup> |
||||
{!showFancy && <TextArea fill={true} readOnly={true} value={log} />} |
||||
{showFancy && <ServerLogFancy log={log} />}</>} |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default ServerLog; |
@ -0,0 +1,96 @@
|
||||
import React, { useEffect, useState } from 'react'; |
||||
import { H1, TextArea, HTMLTable, Switch, Icon, FormGroup, ControlGroup, Intent, H6 } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
|
||||
const ServerLogFancy = observer(props => { |
||||
|
||||
const [fancyLog, setFancyLog] = useState([]); |
||||
const [showError, setShowError] = useState(true); |
||||
const [showTrace, setShowTrace] = useState(true); |
||||
const [showWarn, setShowWarn] = useState(true); |
||||
const [showInfo, setShowInfo] = useState(true); |
||||
const [revert, setRevert] = useState(false); |
||||
|
||||
useEffect(() => { |
||||
processLog(); |
||||
}, [showError, showTrace, revert]); |
||||
|
||||
function processLog() { |
||||
let result = []; |
||||
props.log.split('\n').slice(1).slice(0, -1).map(string => { |
||||
let arr = string.split('|'); |
||||
result.push({ |
||||
date: arr[0], |
||||
level: arr[1], |
||||
module: arr[2], |
||||
message: arr[3], |
||||
}) |
||||
}); |
||||
|
||||
if (!showTrace) { |
||||
result = result.filter(str => str.level != 'TRACE'); |
||||
} |
||||
if (!showError) { |
||||
result = result.filter(str => str.level != 'ERROR'); |
||||
} |
||||
if (!showWarn) { |
||||
result = result.filter(str => str.level != 'WARN'); |
||||
} |
||||
if (!showInfo) { |
||||
result = result.filter(str => str.level != 'INFO'); |
||||
} |
||||
|
||||
if (revert) { |
||||
result.reverse(); |
||||
} |
||||
|
||||
setFancyLog(result); |
||||
} |
||||
|
||||
const options = <> |
||||
<div> |
||||
<Switch checked={showError} label="ERROR" onChange={(_) => setShowError(!showError)} /> |
||||
<Switch checked={showTrace} label="TRACE" onChange={(_) => setShowTrace(!showTrace)} /> |
||||
<Switch checked={showWarn} label="WARN" onChange={(_) => setShowWarn(!showWarn)} /> |
||||
<Switch checked={showInfo} label="INFO" onChange={(_) => setShowInfo(!showInfo)} /> |
||||
|
||||
<Switch checked={revert} label="В обратном порядке" onChange={(_) => setRevert(!revert)} /> |
||||
</div> |
||||
<H6>Всего: {fancyLog.length}</H6></> |
||||
|
||||
const trace = <><Icon icon="tick" intent={Intent.SUCCESS} /><span> TRACE</span></> |
||||
const error = <><Icon icon="error" intent={Intent.DANGER} /><span> ERROR</span></> |
||||
const warn = <><Icon icon="error" intent={Intent.WARNING} /><span> WARN</span></> |
||||
const info = <><Icon icon="info-sign" intent={Intent.PRIMARY} /><span> INFO</span></> |
||||
|
||||
const fancyTable = <> |
||||
<HTMLTable condensed interactive={true}> |
||||
<thead> |
||||
<th>Уровень</th> |
||||
<th>Дата</th> |
||||
<th>Модуль</th> |
||||
<th>Сообщение</th> |
||||
</thead> |
||||
<tbody> |
||||
{(fancyLog.map(str => { |
||||
return <tr> |
||||
<td>{str.level === 'TRACE' && trace}{str.level === 'ERROR' && error} |
||||
{str.level === 'WARN' && warn}{str.level === 'INFO' && info}</td> |
||||
<td>{str.date}</td> |
||||
<td>{str.module}</td> |
||||
<td>{str.message}</td> |
||||
</tr> |
||||
}))} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
|
||||
return (<> |
||||
{options} |
||||
{fancyTable} |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default ServerLogFancy; |
@ -0,0 +1,70 @@
|
||||
import React, { useEffect, useState } from 'react'; |
||||
import { HTMLTable, Breadcrumbs, Breadcrumb, Tag } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import Login from '../stores/LoginStore'; |
||||
import { apiRequest } from '../deepApi'; |
||||
import { useHistory } from 'react-router-dom'; |
||||
|
||||
const ServerLogs = observer(props => { |
||||
|
||||
let history = useHistory(); |
||||
|
||||
const [logs, setLogs] = useState([]); |
||||
|
||||
const { session } = Login; |
||||
|
||||
useEffect(() => { |
||||
getLogs(); |
||||
console.log(logs) |
||||
}, []); |
||||
|
||||
const BREADCRUMBS = [ |
||||
{ href: "/logs", icon: "folder-close", text: "Логи" }, |
||||
]; |
||||
|
||||
function getLogs() { |
||||
const req = { AllFiles: true }; |
||||
apiRequest(req, "api-log-getfileslist", session, (res) => { setLogs(res.FilesList) }); |
||||
} |
||||
|
||||
function formatBytes(bytes, decimals = 2) { |
||||
if (bytes === 0) return '0 байт'; |
||||
|
||||
const k = 1024; |
||||
const dm = decimals < 0 ? 0 : decimals; |
||||
const sizes = ['байт', 'КБ', 'МБ', 'ГБ', 'TB', 'PB', 'EB', 'ZB', 'YB']; |
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; |
||||
} |
||||
|
||||
const renderCurrentBreadcrumb = ({ text, ...restProps }) => { |
||||
return <Breadcrumb {...restProps}>{text}</Breadcrumb>; |
||||
}; |
||||
|
||||
return (<> |
||||
<Breadcrumbs |
||||
currentBreadcrumbRenderer={renderCurrentBreadcrumb} |
||||
items={BREADCRUMBS} |
||||
/> |
||||
<HTMLTable interactive={true}> |
||||
<thead> |
||||
<td>Имя файла</td> |
||||
<td>Размер</td> |
||||
<td>Дата модификации</td> |
||||
</thead> |
||||
<tbody> |
||||
{logs.map(l => |
||||
<tr onClick={(e) => history.push(`/logs/${l.FileName}`)}> |
||||
<td>{l.FileName}</td> |
||||
<td>{formatBytes(l.FileSize)}</td> |
||||
<td>{new Date(l.LastModifyDate).toLocaleDateString()} {new Date(l.LastModifyDate).toLocaleTimeString()}</td> |
||||
</tr>)} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default ServerLogs; |
@ -0,0 +1,72 @@
|
||||
import React from 'react'; |
||||
import { Button, Navbar, NavbarGroup, NavbarHeading, NavbarDivider, Classes, Alignment, Menu, MenuItem, Popover, Position } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import LoginStore from '../stores/LoginStore'; |
||||
import { useHistory, useLocation } from 'react-router-dom'; |
||||
import { DEEP_SERVER, changeServerToDefault } from '../deepApi'; |
||||
import Info from '../stores/ServerInfo'; |
||||
import config from '../config'; |
||||
import LocalStore from '../stores/LocalStore'; |
||||
|
||||
const TopNavbar = observer(props => { |
||||
|
||||
let { logout, isLoggedIn, username } = LoginStore; |
||||
let { switchTheme, darkTheme } = LocalStore; |
||||
|
||||
let history = useHistory(); |
||||
let location = useLocation(); |
||||
|
||||
const { getData, serverInfo, reset } = Info; |
||||
|
||||
const profileMenu = ( |
||||
<Menu> |
||||
<MenuItem icon="user" text="Профиль" onClick={() => history.push("/profile")} /> |
||||
{DEEP_SERVER !== config.api && <MenuItem icon="log-out" text={`Вернуться на ${getHostname(config.api)}`} onClick={handleLogoutToDefault} />} |
||||
<MenuItem icon="log-out" text="Выйти" onClick={handleLogout} /> |
||||
</Menu>); |
||||
|
||||
const authMenu = ( |
||||
<Popover content={profileMenu} position={Position.BOTTOM}> |
||||
<Button className={Classes.MINIMAL} icon="user" text={username} rightIcon="caret-down" /> |
||||
</Popover> |
||||
) |
||||
|
||||
const unAuth = <> |
||||
{(location.pathname === "/login" || location.pathname === "/clogin") ? null : |
||||
<Button className={Classes.MINIMAL} icon="user" text="Войти" onClick={() => history.push("/login")} />} |
||||
</> |
||||
|
||||
function handleLogoutToDefault() { |
||||
changeServerToDefault(); |
||||
reset(); |
||||
getData(); |
||||
handleLogout(); |
||||
} |
||||
|
||||
function handleLogout() { |
||||
logout(); |
||||
history.push("/"); |
||||
} |
||||
|
||||
function getHostname(url) { |
||||
let u = new URL(url); |
||||
return u.hostname; |
||||
} |
||||
|
||||
return ( |
||||
<Navbar className={"bp3-dark"}> |
||||
<NavbarGroup align={Alignment.LEFT}> |
||||
<NavbarHeading><div>👀 ASIP</div><div className={"bp3-text-muted bp3-text-small"}>{getHostname(DEEP_SERVER)}</div></NavbarHeading> |
||||
<NavbarDivider /> |
||||
<Button className={Classes.MINIMAL} icon="home" text="Главная" onClick={() => history.push("/")} /> |
||||
</NavbarGroup> |
||||
<NavbarGroup align={Alignment.RIGHT}> |
||||
<Button className={Classes.MINIMAL} icon={darkTheme ? 'flash' : 'moon'} onClick={() => switchTheme()} /> |
||||
{isLoggedIn && <NavbarDivider />} |
||||
{isLoggedIn ? authMenu : (!!serverInfo.ServerNameFull ? unAuth : null)} |
||||
</NavbarGroup> |
||||
</Navbar> |
||||
); |
||||
}); |
||||
|
||||
export default TopNavbar; |
@ -0,0 +1,35 @@
|
||||
import React, { useEffect, useState } from 'react'; |
||||
import logo from '../logo.svg'; |
||||
//import '../App.css';
|
||||
import { Button, Card, H5, Intent, Callout, Spinner } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import Info from '../stores/ServerInfo'; |
||||
import { useHistory, useLocation, Link } from 'react-router-dom'; |
||||
import config from '../config'; |
||||
|
||||
const UnAuthPage = observer(props => { |
||||
let history = useHistory(); |
||||
|
||||
const { counter, serverStatus, serverInfo, fetchingData } = Info; |
||||
|
||||
const cardStyle = { |
||||
width: 400, |
||||
alignSelf: 'center', |
||||
margin: 'auto' |
||||
} |
||||
|
||||
const ok = <Card> |
||||
<H5>{serverInfo.ServerNameShort}</H5> |
||||
<p>{serverInfo.ServerNameFull}</p> |
||||
<Button onClick={() => history.push("/login")}>Войти</Button> |
||||
</Card> |
||||
|
||||
const fail = <Callout intent={Intent.WARNING}> |
||||
<p>Сервер <a href={config.api}>{config.api}</a> не отвечает</p> |
||||
{config.allowMultiLogin && <Button onClick={() => history.push("/clogin")}>Вход на другой сервер</Button>} |
||||
</Callout> |
||||
|
||||
return <div style={cardStyle}>{(fetchingData ? <Spinner /> : serverStatus ? ok : fail)}</div> |
||||
}); |
||||
|
||||
export default UnAuthPage; |
@ -0,0 +1,88 @@
|
||||
import React, { useEffect } from 'react'; |
||||
import { H1, HTMLTable, Tab, Tabs, Tag, Switch } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import Login from '../stores/LoginStore'; |
||||
import LocalStore from '../stores/LocalStore'; |
||||
|
||||
const UserInfo = observer(props => { |
||||
|
||||
const { userInfo, getUserInfo, isAdmin, session } = Login; |
||||
let { changeThemeOnTime, setChangeThemeOnTime } = LocalStore; |
||||
|
||||
useEffect(() => { |
||||
getUserInfo(); |
||||
}, []); |
||||
|
||||
let date = withoutTime(userInfo.BIRTH_DATE); |
||||
let today = withoutTime(new Date()); |
||||
|
||||
let cake = date.valueOf() === today.valueOf() ? " 🎂" : null; |
||||
|
||||
function withoutTime(datetime) { |
||||
var d = new Date(datetime); |
||||
d.setHours(0, 0, 0, 0); |
||||
return d; |
||||
} |
||||
|
||||
const info = <> |
||||
<HTMLTable> |
||||
<tbody> |
||||
<tr> |
||||
<td>Логин</td> |
||||
<td>{userInfo.U_NAME}</td> |
||||
</tr> |
||||
{isAdmin && |
||||
<tr> |
||||
<td>Сессия</td> |
||||
<td><code>{session}</code></td> |
||||
</tr>} |
||||
<tr> |
||||
<td>Полное имя</td> |
||||
<td>{userInfo.U_FULLNAME}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Пол</td> |
||||
<td>{userInfo.U_SEX}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Почта</td> |
||||
<td>{userInfo.U_EMAIL}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Телефон</td> |
||||
<td>{userInfo.U_PHONE_NUMS}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Дата рождения</td> |
||||
<td>{date.toLocaleDateString()}{cake}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Офис</td> |
||||
<td>{userInfo.UX_OFFICE}</td> |
||||
</tr> |
||||
<tr> |
||||
<td>Комментарий</td> |
||||
<td>{userInfo.UX_DESCRIPTION}</td> |
||||
</tr> |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
const perms = <p>{!!userInfo.PermissionNames ? userInfo.PermissionNames.map(p => `${p}, `) : null}</p>; |
||||
const permsCaption = <>Разрешения <Tag minimal={true}>{!!userInfo.PermissionNames ? userInfo.PermissionNames.length : 0}</Tag></> |
||||
|
||||
const misc = <Switch checked={changeThemeOnTime} label="Менять тему в зависимости от времени суток" onChange={(_) => setChangeThemeOnTime(!changeThemeOnTime)} /> |
||||
|
||||
return (<> |
||||
<H1>{userInfo.U_FULLNAME}</H1> |
||||
<Tabs id="Tabs"> |
||||
<Tab id="info" title="Информация" panel={info} /> |
||||
<Tab id="perms" title={permsCaption} panel={perms} /> |
||||
<Tab id="misc" title="Всякая фигня" panel={misc} /> |
||||
</Tabs> |
||||
|
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default UserInfo; |
@ -0,0 +1,55 @@
|
||||
import React, { useEffect } from 'react'; |
||||
import { H1, H2, HTMLTable, Tab, Tabs, Tag } from '@blueprintjs/core'; |
||||
import { observer } from 'mobx-react'; |
||||
import Users from '../stores/UsersStore'; |
||||
|
||||
const UserInfo = observer(props => { |
||||
|
||||
const { users, getUsers } = Users; |
||||
|
||||
useEffect(() => { |
||||
getUsers(); |
||||
}, []) |
||||
|
||||
const usersList = <> |
||||
<HTMLTable interactive={true}> |
||||
<thead> |
||||
<th></th> |
||||
<th>ID</th> |
||||
<th>Логин</th> |
||||
<th>Полное имя</th> |
||||
<th>Пол</th> |
||||
<th>Почта</th> |
||||
<th>Телефон</th> |
||||
<th>Дата рождения</th> |
||||
<th>Офис</th> |
||||
<th>Комментарий</th> |
||||
</thead> |
||||
<tbody> |
||||
{users.Users.map(u => { |
||||
return <tr> |
||||
<td>{!!u.TI_DELETED && "❌"}{!!u.TI_LOCKED && "🔒"}</td> |
||||
<td>{u.ID}</td> |
||||
<td>{u.U_NAME}</td> |
||||
<td>{u.U_FULLNAME}</td> |
||||
<td>{u.U_SEX}</td> |
||||
<td>{u.U_EMAIL}</td> |
||||
<td>{u.U_PHONE_NUMS}</td> |
||||
<td>{new Date(u.BIRTH_DATE).toLocaleDateString()}</td> |
||||
<td>{u.UX_OFFICE}</td> |
||||
<td>{u.UX_DESCRIPTION}</td> |
||||
</tr> |
||||
})} |
||||
</tbody> |
||||
</HTMLTable> |
||||
</> |
||||
|
||||
|
||||
return (<> |
||||
<H1>Пользователи <Tag minimal={true}>{users.Users.length}</Tag></H1> |
||||
{usersList} |
||||
</> |
||||
); |
||||
}); |
||||
|
||||
export default UserInfo; |
@ -0,0 +1,4 @@
|
||||
{ |
||||
"api": "http://citadel.tias.pro:48910", |
||||
"allowMultiLogin": true |
||||
} |
@ -0,0 +1,4 @@
|
||||
{ |
||||
"api": "https://soenergo-stroy.tias.dev", |
||||
"allowMultiLogin": true |
||||
} |
@ -0,0 +1,5 @@
|
||||
if (process.env.NODE_ENV === 'development') { |
||||
module.exports = require('./config.dev.json') |
||||
} else { |
||||
module.exports = require('./config.prod.json') |
||||
} |
@ -0,0 +1,92 @@
|
||||
import config from './config'; |
||||
|
||||
export let DEEP_SERVER = config.api; |
||||
|
||||
export function changeServerToDefault() { |
||||
DEEP_SERVER = config.api |
||||
} |
||||
|
||||
export function apiUnAuthRequest(reqBody, apiName, callback) { |
||||
return apiRequest(reqBody, apiName, null, callback, null, DEEP_SERVER) |
||||
} |
||||
|
||||
export function apiUnAuthCustomRequest(reqBody, apiName, customServer, callback, onError, onDone) { |
||||
return apiRequest(reqBody, apiName, null, callback, onError, onDone, customServer) |
||||
} |
||||
|
||||
export function changeServer(server) { |
||||
DEEP_SERVER = server; |
||||
} |
||||
|
||||
export function apiRequest(reqBody, apiName, session, callback, onError, onDone, server, onProgress) { |
||||
let api = server; |
||||
|
||||
if (!!server === false) { |
||||
api = DEEP_SERVER; |
||||
} |
||||
console.log(`Call to: ${api}/${apiName}`); |
||||
|
||||
var xhr = new XMLHttpRequest(); |
||||
|
||||
try { |
||||
xhr.open("POST", `${api}/${apiName}`); |
||||
} catch (error) { |
||||
if (process.env.NODE_ENV === "development") { |
||||
console.log(error); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
xhr.setRequestHeader('Content-Type', 'application/json'); |
||||
if (session) { |
||||
xhr.setRequestHeader('X-deep-session', session); |
||||
} |
||||
|
||||
xhr.onreadystatechange = function (e) { |
||||
if (xhr.readyState === 4) { |
||||
if (xhr.status === 200) { |
||||
var res = JSON.parse(xhr.responseText); |
||||
|
||||
if (process.env.NODE_ENV === "development") { |
||||
console.log(res); |
||||
} |
||||
|
||||
if (res === 'OK') { |
||||
onError('Сервер вернул \"OK\"'); |
||||
} else { |
||||
callback(res); |
||||
} |
||||
} |
||||
else { |
||||
if (process.env.NODE_ENV === "development") { |
||||
console.log(res); |
||||
} |
||||
|
||||
if (onError != null) { |
||||
if (xhr.status === 0) { |
||||
onError('Ошибка соединения'); |
||||
} else { |
||||
onError(xhr.status + ': ' + xhr.statusText); |
||||
} |
||||
} |
||||
} |
||||
if (onDone && typeof (onDone) === 'function') onDone(); |
||||
} |
||||
}; |
||||
|
||||
xhr.onerror = () => { |
||||
if (onError && typeof (onError) === 'function') onError('Ошибка соединения'); |
||||
} |
||||
|
||||
xhr.onprogress = (e) => { |
||||
if (onProgress && typeof (onProgress) === 'function' && e.lengthComputable) { |
||||
var percentComplete = e.loaded / e.total * 100; |
||||
onProgress(percentComplete); |
||||
} |
||||
} |
||||
|
||||
if (process.env.NODE_ENV === "development") { |
||||
console.log(reqBody); |
||||
} |
||||
xhr.send(JSON.stringify(reqBody)); |
||||
} |
@ -0,0 +1,38 @@
|
||||
import { useState } from 'react'; |
||||
|
||||
// Hook
|
||||
export function useLocalStorage(key, initialValue) { |
||||
// State to store our value
|
||||
// Pass initial state function to useState so logic is only executed once
|
||||
const [storedValue, setStoredValue] = useState(() => { |
||||
try { |
||||
// Get from local storage by key
|
||||
const item = window.localStorage.getItem(key); |
||||
// Parse stored json or if none return initialValue
|
||||
return item ? JSON.parse(item) : initialValue; |
||||
} catch (error) { |
||||
// If error also return initialValue
|
||||
console.log(error); |
||||
return initialValue; |
||||
} |
||||
}); |
||||
|
||||
// Return a wrapped version of useState's setter function that ...
|
||||
// ... persists the new value to localStorage.
|
||||
const setValue = value => { |
||||
try { |
||||
// Allow value to be a function so we have same API as useState
|
||||
const valueToStore = |
||||
value instanceof Function ? value(storedValue) : value; |
||||
// Save state
|
||||
setStoredValue(valueToStore); |
||||
// Save to local storage
|
||||
window.localStorage.setItem(key, JSON.stringify(valueToStore)); |
||||
} catch (error) { |
||||
// A more advanced implementation would handle the error case
|
||||
console.log(error); |
||||
} |
||||
}; |
||||
|
||||
return [storedValue, setValue]; |
||||
} |
@ -1,13 +1,3 @@
|
||||
body { |
||||
margin: 0; |
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', |
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', |
||||
sans-serif; |
||||
-webkit-font-smoothing: antialiased; |
||||
-moz-osx-font-smoothing: grayscale; |
||||
} |
||||
|
||||
code { |
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', |
||||
monospace; |
||||
} |
||||
@import "~normalize.css"; |
||||
@import "~@blueprintjs/core/lib/css/blueprint.css"; |
||||
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css"; |
@ -0,0 +1,29 @@
|
||||
import { observable, action, decorate } from 'mobx'; |
||||
import { apiRequest } from '../deepApi' |
||||
import LoginStore from './LoginStore'; |
||||
|
||||
class CallsStore { |
||||
objects = []; |
||||
|
||||
getAllCalls = () => { |
||||
let { session } = LoginStore; |
||||
const req = { GetAll: true, CategoryID: 10004 }; |
||||
apiRequest(req, "api-objects-get", session, (res) => { |
||||
res.Objects.filter(o => [1004, 1005, 1006].includes(o.ID_TYPE)).forEach(o => { |
||||
const req = { CategoryID: o.ID_CAT, ObjectID: o.ID }; |
||||
apiRequest(req, "api-objects-getpars", session, (res) => { |
||||
o.Pars = res.Pars; |
||||
this.objects.push(o); |
||||
console.log(o.ID); |
||||
}); |
||||
}); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
decorate(CallsStore, { |
||||
objects: observable, |
||||
getAllObjectsByCatID: action, |
||||
}); |
||||
|
||||
export default new CallsStore(); |
@ -0,0 +1,21 @@
|
||||
import { observable, action, decorate } from 'mobx'; |
||||
|
||||
class ValueStore { |
||||
count = 0; |
||||
decrease = () => { |
||||
this.counter = --this.count; |
||||
} |
||||
increase = () => { |
||||
this.counter = ++this.count; |
||||
} |
||||
} |
||||
|
||||
decorate(ValueStore, { |
||||
count: observable, |
||||
decrease: action, |
||||
increase: action, |
||||
}); |
||||
|
||||
|
||||
const Store = new ValueStore(); |
||||
export default Store; |
@ -0,0 +1,72 @@
|
||||
import { observable, action, decorate, computed, when } from 'mobx'; |
||||
import moment from 'moment'; |
||||
|
||||
class LocalStore { |
||||
darkTheme = false; |
||||
changeThemeOnTime = true; |
||||
|
||||
time = moment(); |
||||
|
||||
constructor(period = 1000 * 60) { |
||||
this.interval = setInterval(() => this.clockTick(), period); |
||||
if (localStorage.getItem('theme') === 'dark') { |
||||
this.darkTheme = true; |
||||
} |
||||
|
||||
if (localStorage.getItem('changeThemeOnTime') === 'no') { |
||||
this.changeThemeOnTime = false; |
||||
} |
||||
|
||||
if (this.changeThemeOnTime) { |
||||
when( |
||||
() => this.time.isAfter(moment('22:00', "hh:mm")), |
||||
() => { |
||||
localStorage.setItem('theme', 'dark'); |
||||
this.darkTheme = true; |
||||
} |
||||
) |
||||
when( |
||||
() => this.time.isAfter(moment('06:00', "hh:mm")) && this.time.isBefore(moment('22:00', "hh:mm")), |
||||
() => { |
||||
localStorage.setItem('theme', 'light'); |
||||
this.darkTheme = false; |
||||
} |
||||
) |
||||
} |
||||
} |
||||
|
||||
clockTick(newTime = moment()) { |
||||
this.time = newTime; |
||||
} |
||||
|
||||
switchTheme = () => { |
||||
if (this.darkTheme) { |
||||
localStorage.setItem('theme', 'light'); |
||||
this.darkTheme = false; |
||||
} else { |
||||
localStorage.setItem('theme', 'dark'); |
||||
this.darkTheme = true; |
||||
} |
||||
} |
||||
|
||||
setChangeThemeOnTime = (change) => { |
||||
if (change) { |
||||
localStorage.setItem('changeThemeOnTime', 'yes'); |
||||
this.changeThemeOnTime = true; |
||||
} else { |
||||
localStorage.setItem('changeThemeOnTime', 'no'); |
||||
this.changeThemeOnTime = false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
decorate(LocalStore, { |
||||
darkTheme: observable, |
||||
switchTheme: action, |
||||
clockTick: action, |
||||
time: observable, |
||||
setChangeThemeOnTime: action, |
||||
changeThemeOnTime: observable |
||||
}); |
||||
|
||||
export default new LocalStore(); |
@ -0,0 +1,72 @@
|
||||
import { observable, action, decorate, computed } from 'mobx'; |
||||
import { apiUnAuthRequest, apiRequest } from '../deepApi' |
||||
|
||||
class LoginStore { |
||||
username; |
||||
password; |
||||
session; |
||||
userInfo = {}; |
||||
incorrectPass; |
||||
|
||||
login = (username, password) => { |
||||
this.username = username; |
||||
this.password = password; |
||||
|
||||
const req = { |
||||
UserName: this.username, |
||||
PassWord: this.password |
||||
}; |
||||
|
||||
apiUnAuthRequest(req, "user-login", (res) => { |
||||
if (res.ResultCode === 1) { |
||||
this.session = res.SessionID; |
||||
return true; |
||||
} |
||||
if (res.ResultCode === 6001) { |
||||
this.incorrectPass = true; |
||||
return true; |
||||
} |
||||
return false; |
||||
}); |
||||
} |
||||
|
||||
logout = () => { |
||||
this.username = undefined; |
||||
this.password = undefined; |
||||
this.session = undefined; |
||||
this.incorrectPass = undefined; |
||||
} |
||||
|
||||
getUserInfo = () => { |
||||
apiRequest(null, "user-info", this.session, (res) => { this.userInfo = res }); |
||||
} |
||||
|
||||
get isLoggedIn() { |
||||
return !!this.session; |
||||
} |
||||
|
||||
hasPermission = (permission) => { |
||||
if (!this.userInfo || !this.userInfo.PermissionNames) return false; |
||||
return this.userInfo.PermissionNames.includes(permission); |
||||
} |
||||
|
||||
get isAdmin() { |
||||
if (!this.userInfo || !this.userInfo.PermissionNames) return false; |
||||
return this.userInfo.PermissionNames.includes('canAdminUsers'); |
||||
} |
||||
} |
||||
|
||||
decorate(LoginStore, { |
||||
username: observable, |
||||
password: observable, |
||||
session: observable, |
||||
userInfo: observable, |
||||
incorrectPass: observable, |
||||
login: action, |
||||
logout: action, |
||||
isLoggedIn: computed, |
||||
hasPermission: action, |
||||
getUserInfo: action, |
||||
}); |
||||
|
||||
export default new LoginStore(); |
@ -0,0 +1,122 @@
|
||||
import { observable, action, decorate, extendObservable, computed } from 'mobx'; |
||||
import { apiRequest } from '../deepApi' |
||||
import LoginStore from './LoginStore'; |
||||
|
||||
class ObjectsStore { |
||||
categories = []; |
||||
objects = []; |
||||
types = []; |
||||
fetching = false; |
||||
currentObject; |
||||
|
||||
constructor() { |
||||
this.getCategories(); |
||||
this.getTypes(); |
||||
} |
||||
|
||||
getCategories = () => { |
||||
let { session } = LoginStore; |
||||
const req = { GetAll: true }; |
||||
apiRequest(req, "api-categories-get", session, (res) => { this.categories = res.Categories }); |
||||
} |
||||
|
||||
get getFilteredCategories() { |
||||
return this.categories; |
||||
} |
||||
|
||||
getTypes = () => { |
||||
let { session } = LoginStore; |
||||
const req = { GetAll: true }; |
||||
apiRequest(req, "api-types-get", session, (res) => { this.types = res.ObjectTypes }); |
||||
} |
||||
|
||||
getAllObjects = () => { |
||||
let { session } = LoginStore; |
||||
const req = { GetAll: true, CategoryID: 10001 }; |
||||
this.fetching = true; |
||||
apiRequest(req, "api-objects-get", session, (res) => { this.objects = res.Objects; this.fetching = false; }); |
||||
} |
||||
|
||||
getAllObjectsByCatID = (catID) => { |
||||
let { session } = LoginStore; |
||||
const req = { GetAll: true, CategoryID: catID }; |
||||
this.fetching = true; |
||||
apiRequest(req, "api-objects-get", session, (res) => { |
||||
res.Objects.forEach(o => { |
||||
const req = { CategoryID: o.ID_CAT, ObjectID: o.ID }; |
||||
apiRequest(req, "api-objects-getpars", session, (res) => { |
||||
o.Pars = res.Pars; |
||||
this.objects.push(o); |
||||
}); |
||||
this.fetching = false; |
||||
}) |
||||
}); |
||||
} |
||||
|
||||
getObjects = (searchMask, catIDs) => { |
||||
let { session } = LoginStore; |
||||
this.objects.clear(); |
||||
this.fetching = true; |
||||
const req = { SearchMask: `%${searchMask}%`, CategoryIDs: catIDs }; |
||||
apiRequest(req, "api-objects-search", session, (res) => { |
||||
if (res.FoundObjectKeys.length === 0) { |
||||
this.fetching = false; |
||||
} else { |
||||
res.FoundObjectKeys.map(k => { |
||||
const req = { GetAll: false, ObjectIDs: [k.ID], CategoryID: k.ID_CAT }; |
||||
apiRequest(req, "api-objects-get", session, (resObj) => { |
||||
this.objects.push(resObj.Objects[0]); |
||||
if (k === res.FoundObjectKeys[res.FoundObjectKeys.length - 1]) { |
||||
this.fetching = false; |
||||
} |
||||
}); |
||||
}) |
||||
} |
||||
}); |
||||
} |
||||
|
||||
getObject = (id, idCat) => { |
||||
let { session } = LoginStore; |
||||
this.fetching = true; |
||||
const req = { GetAll: false, ObjectIDs: id, CategoryID: idCat }; |
||||
apiRequest(req, "api-objects-get", session, (resObj) => { |
||||
this.currentObject = resObj.Objects[0]; |
||||
this.fetching = false; |
||||
}); |
||||
} |
||||
|
||||
getPars = (object) => { |
||||
let { session } = LoginStore; |
||||
const req = { CategoryID: object.ID_CAT, ObjectID: object.ID }; |
||||
|
||||
apiRequest(req, "api-objects-getpars", session, (res) => { |
||||
let o = this.objects.find(o => o === object); |
||||
if (o) { |
||||
extendObservable(o, res); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
getTypeByID = (id) => { |
||||
return this.types.filter(t => t.ID_TYPE === id)[0]; |
||||
} |
||||
} |
||||
|
||||
decorate(ObjectsStore, { |
||||
categories: observable, |
||||
objects: observable, |
||||
types: observable, |
||||
getAllObjects: action, |
||||
getCategories: action, |
||||
getObjects: action, |
||||
getTypes: action, |
||||
getTypeByID: action, |
||||
fetching: observable, |
||||
getPars: action, |
||||
getAllObjectsByCatID: action, |
||||
getObject: action, |
||||
currentObject: observable, |
||||
getFilteredCategories: computed |
||||
}); |
||||
|
||||
export default new ObjectsStore(); |
@ -0,0 +1,52 @@
|
||||
import { observable, action, decorate } from 'mobx'; |
||||
import { apiUnAuthRequest, apiRequest } from '../deepApi' |
||||
import LoginStore from './LoginStore'; |
||||
|
||||
class Info { |
||||
serverInfo = {}; |
||||
instanceSettings = {} |
||||
counter = 10; |
||||
serverStatus; |
||||
fetchingData = false; |
||||
|
||||
constructor() { |
||||
this.getData(); |
||||
} |
||||
|
||||
getData = () => { |
||||
this.fetchingData = true; |
||||
this.counter++; |
||||
apiUnAuthRequest(null, "sys-status", (res) => { |
||||
this.serverInfo = res; |
||||
|
||||
if (!!this.serverInfo.ServerNameFull) { |
||||
this.serverStatus = true; |
||||
} |
||||
this.fetchingData = false; |
||||
}); |
||||
} |
||||
|
||||
getInstanceSettings = () => { |
||||
let { session } = LoginStore; |
||||
this.counter++; |
||||
apiRequest(null, "api-settings-instance-get", session, (res) => { this.instanceSettings = res }); |
||||
} |
||||
|
||||
reset = () => { |
||||
this.serverInfo = {}; |
||||
this.instanceSettings = {} |
||||
this.counter = 10; |
||||
this.serverStatus = undefined; |
||||
} |
||||
} |
||||
|
||||
decorate(Info, { |
||||
serverInfo: observable, |
||||
counter: observable, |
||||
serverStatus: observable, |
||||
fetchingData: observable, |
||||
getData: action, |
||||
reset: action, |
||||
}); |
||||
|
||||
export default new Info(); |
@ -0,0 +1,74 @@
|
||||
import { observable, action, decorate } from 'mobx'; |
||||
import { apiUnAuthCustomRequest } from '../deepApi' |
||||
|
||||
class ServerStore { |
||||
servers = []; |
||||
|
||||
getServers = () => { |
||||
let s = JSON.parse(localStorage.getItem('servers')); |
||||
if (!Array.isArray(s)) return; |
||||
this.servers = s; |
||||
} |
||||
|
||||
addServer = (server, status, message) => { |
||||
//toto pамена
|
||||
if (this.servers.findIndex(x => x.server.toLowerCase() === server.trim().toLowerCase()) >= 0) { |
||||
return; |
||||
} |
||||
|
||||
this.servers.push({ |
||||
server: server.trim(), |
||||
status: status, |
||||
result: message, |
||||
}); |
||||
this.checkServer(server); |
||||
localStorage.setItem('servers', JSON.stringify(this.servers)); |
||||
} |
||||
|
||||
clearServers = () => { |
||||
this.servers = []; |
||||
localStorage.setItem('servers', JSON.stringify(this.servers)); |
||||
} |
||||
|
||||
removeServer = (server) => { |
||||
let foundIndex = this.servers.findIndex(x => x.server === server.server); |
||||
this.servers.splice(foundIndex, 1) |
||||
localStorage.setItem('servers', JSON.stringify(this.servers)); |
||||
} |
||||
|
||||
checkServer = (server) => { |
||||
let checked = { |
||||
server: server, |
||||
date: new Date().toUTCString(), |
||||
}; |
||||
|
||||
let msg; |
||||
|
||||
apiUnAuthCustomRequest(null, "sys-status", checked.server, |
||||
(res) => { checked.status = true; msg = res.ServerNameShort }, |
||||
(err) => { checked.status = false; msg = err }, |
||||
() => { |
||||
checked.result = msg; |
||||
let foundIndex = this.servers.findIndex(x => x.server == checked.server); |
||||
this.servers[foundIndex] = checked; |
||||
localStorage.setItem('servers', JSON.stringify(this.servers)); |
||||
}); |
||||
} |
||||
|
||||
checkServers = () => { |
||||
let checked = this.servers.map(s => this.checkServer(s.server)); |
||||
localStorage.setItem('servers', JSON.stringify(checked)); |
||||
} |
||||
} |
||||
|
||||
decorate(ServerStore, { |
||||
servers: observable, |
||||
getServers: action, |
||||
addServer: action, |
||||
clearServers: action, |
||||
removeServer: action, |
||||
checkServer: action, |
||||
checkServers: action, |
||||
}); |
||||
|
||||
export default new ServerStore(); |
@ -0,0 +1,22 @@
|
||||
import { observable, action, decorate } from 'mobx'; |
||||
import { apiUnAuthRequest, apiRequest } from '../deepApi' |
||||
import LoginStore from './LoginStore'; |
||||
|
||||
class UsersStore { |
||||
users = { |
||||
Users: [] |
||||
}; |
||||
|
||||
getUsers = () => { |
||||
let { session } = LoginStore; |
||||
const req = { GetAll: true }; |
||||
apiRequest(req, "api-users-get", session, (res) => { this.users = res }); |
||||
} |
||||
} |
||||
|
||||
decorate(UsersStore, { |
||||
users: observable, |
||||
getUsers: action |
||||
}); |
||||
|
||||
export default new UsersStore(); |
Loading…
Reference in new issue