Browse Source

First commit

pull/1/head
parent
commit
dc2834963a
  1. 9
      .editorconfig
  2. 15
      .vscode/launch.json
  3. 25
      README.md
  4. 4
      deploy.sh
  5. 3978
      package-lock.json
  6. 7
      package.json
  7. 56
      public/index.html
  8. 121
      src/App.js
  9. 3
      src/common.js
  10. 107
      src/components/AboutServer.js
  11. 109
      src/components/Calls.js
  12. 44
      src/components/Categories.js
  13. 14
      src/components/Dashboard.js
  14. 31
      src/components/ErrorPage.js
  15. 29
      src/components/Example.js
  16. 115
      src/components/FavServers.js
  17. 83
      src/components/LeftMenu.js
  18. 230
      src/components/Login.js
  19. 86
      src/components/Object.js
  20. 54
      src/components/ObjectTypes.js
  21. 217
      src/components/Objects.js
  22. 18
      src/components/PermsTest.js
  23. 51
      src/components/PrivateRoute.js
  24. 67
      src/components/ServerInfo.js
  25. 66
      src/components/ServerLog.js
  26. 96
      src/components/ServerLogFancy.js
  27. 70
      src/components/ServerLogs.js
  28. 72
      src/components/TopNavbar.js
  29. 35
      src/components/UnAuthPage.js
  30. 88
      src/components/UserInfo.js
  31. 55
      src/components/Users.js
  32. 4
      src/config/config.dev.json
  33. 4
      src/config/config.prod.json
  34. 5
      src/config/index.js
  35. 92
      src/deepApi.js
  36. 38
      src/hooks/useLocalStorage.js
  37. 16
      src/index.css
  38. 29
      src/stores/CallsStore.js
  39. 21
      src/stores/LikesStore.js
  40. 72
      src/stores/LocalStore.js
  41. 72
      src/stores/LoginStore.js
  42. 122
      src/stores/ObjectsStore.js
  43. 52
      src/stores/ServerInfo.js
  44. 74
      src/stores/ServersStore.js
  45. 22
      src/stores/UsersStore.js
  46. 5
      src/styles.js

9
.editorconfig

@ -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

15
.vscode/launch.json vendored

@ -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}"
}
]
}

25
README.md

@ -1,3 +1,28 @@
## Создание страниц
* Скопируйте файл `components/Example.js`, там находится пример вызова АПИ
* Добавьте маршрут в `App.js`:
```<PrivateRoute path="/example" component={withMenu(Example)} />```
или для страницы с разрешением:
```<PrivateRoute path="/example" component={withMenu(Example)} permission={'PERM'} />```
* Добавьте ссылку в левое меню где-то в `components/LeftMenu.js`:
```{ link: "/example", caption: "Моя страница" },```
## Деплой
Имеется скрипт `deploy.sh`, для его работы необходимо
завести конфигурацию `bastion` в `~/.ssh/config`:
Host bastion
Hostname 92.53.67.154
User root
IdentityFile "path/to/id_rsa"
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts

4
deploy.sh

@ -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'

3978
package-lock.json generated

File diff suppressed because it is too large Load Diff

7
package.json

@ -3,11 +3,18 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@blueprintjs/core": "^3.24.0",
"@blueprintjs/select": "^3.13.3",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"mobx": "^5.15.4",
"mobx-react": "^6.2.1",
"moment": "^2.27.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-hotkeys": "^2.0.0-pre9",
"react-router-dom": "^5.1.2",
"react-scripts": "3.4.1"
},
"scripts": {

56
public/index.html

@ -1,21 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
@ -24,12 +22,27 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
<title>React App</title>
<link href="node_modules/normalize.css/normalize.css" rel="stylesheet" />
<!-- blueprint-icons.css file must be included alongside blueprint.css! -->
<link href="node_modules/@blueprintjs/icons/lib/css/blueprint-icons.css" rel="stylesheet" />
<link href="../node_modules/@blueprintjs/core/lib/css/blueprint.css" rel="stylesheet" />
<style>
.bp3-dark {
background-color: #293742;
}
.bp3-body, .bp3-dark {
transition: 0.2s;
}
</style>
<!-- add other blueprint-*.css files here -->
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
@ -39,5 +52,6 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
</body>
</html>

121
src/App.js

@ -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;

3
src/common.js

@ -0,0 +1,3 @@
export function objectIsEmpty (o) {
return o === null || o === undefined || !Object.keys(o).length;
}

107
src/components/AboutServer.js

@ -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;

109
src/components/Calls.js

@ -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;

44
src/components/Categories.js

@ -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;

14
src/components/Dashboard.js

@ -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;

31
src/components/ErrorPage.js

@ -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">&larr;&thinsp;Назад</Link> &middot; <Link onClick={goMain} className="error-link">На главную</Link>
</p>
</div>
)
}
export default ErrorPage;

29
src/components/Example.js

@ -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;

115
src/components/FavServers.js

@ -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;

83
src/components/LeftMenu.js

@ -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;

230
src/components/Login.js

@ -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;

86
src/components/Object.js

@ -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;

54
src/components/ObjectTypes.js

@ -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;

217
src/components/Objects.js

@ -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;

18
src/components/PermsTest.js

@ -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>&nbsp;
<Icon icon={'endorsed'} intent={Intent.SUCCESS} />
</p>
</>
);
});
export default PermsTest;

51
src/components/PrivateRoute.js

@ -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;

67
src/components/ServerInfo.js

@ -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;

66
src/components/ServerLog.js

@ -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;

96
src/components/ServerLogFancy.js

@ -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;

70
src/components/ServerLogs.js

@ -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;

72
src/components/TopNavbar.js

@ -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;

35
src/components/UnAuthPage.js

@ -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;

88
src/components/UserInfo.js

@ -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;

55
src/components/Users.js

@ -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;

4
src/config/config.dev.json

@ -0,0 +1,4 @@
{
"api": "http://citadel.tias.pro:48910",
"allowMultiLogin": true
}

4
src/config/config.prod.json

@ -0,0 +1,4 @@
{
"api": "https://soenergo-stroy.tias.dev",
"allowMultiLogin": true
}

5
src/config/index.js

@ -0,0 +1,5 @@
if (process.env.NODE_ENV === 'development') {
module.exports = require('./config.dev.json')
} else {
module.exports = require('./config.prod.json')
}

92
src/deepApi.js

@ -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));
}

38
src/hooks/useLocalStorage.js

@ -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];
}

16
src/index.css

@ -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";

29
src/stores/CallsStore.js

@ -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();

21
src/stores/LikesStore.js

@ -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;

72
src/stores/LocalStore.js

@ -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();

72
src/stores/LoginStore.js

@ -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();

122
src/stores/ObjectsStore.js

@ -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();

52
src/stores/ServerInfo.js

@ -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();

74
src/stores/ServersStore.js

@ -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();

22
src/stores/UsersStore.js

@ -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();

5
src/styles.js

@ -0,0 +1,5 @@
export const cardStyle = {
width: 400,
alignSelf: 'center',
margin: 'auto'
}
Loading…
Cancel
Save