mirror of
https://github.com/Segate-ekb/publicator.git
synced 2024-11-24 08:02:30 +02:00
Заливаю первую итерацию нового фронта
This commit is contained in:
parent
412698851e
commit
afc2813912
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,3 +2,6 @@
|
||||
/webserver/distr/*
|
||||
!/webserver/distr/.gitkeep
|
||||
/volumes/config.json
|
||||
node_modules
|
||||
|
||||
/react_frontend/build
|
||||
|
27
react_frontend/.gitignore
vendored
Normal file
27
react_frontend/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
/.vscode
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
44
react_frontend/README.md
Normal file
44
react_frontend/README.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Material UI - Create React App example
|
||||
|
||||
## How to use
|
||||
|
||||
Download the example [or clone the repo](https://github.com/mui/material-ui):
|
||||
|
||||
<!-- #default-branch-switch -->
|
||||
|
||||
```sh
|
||||
curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2 material-ui-master/examples/material-cra
|
||||
cd material-cra
|
||||
```
|
||||
|
||||
Install it and run:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
<!-- #default-branch-switch -->
|
||||
|
||||
[![Edit on CodeSandbox](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/mui/material-ui/tree/master/examples/material-cra)
|
||||
|
||||
<!-- #default-branch-switch -->
|
||||
|
||||
[![Edit on StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/mui/material-ui/tree/master/examples/material-cra)
|
||||
|
||||
## The idea behind the example
|
||||
|
||||
<!-- #default-branch-switch -->
|
||||
|
||||
This example demonstrates how you can use [Create React App](https://github.com/facebookincubator/create-react-app) with Material UI.
|
||||
It includes `@mui/material` and its peer dependencies, including [Emotion](https://emotion.sh/docs/introduction), the default style engine in Material UI v5.
|
||||
If you prefer, you can [use styled-components instead](https://mui.com/material-ui/guides/interoperability/#styled-components).
|
||||
|
||||
## What's next?
|
||||
|
||||
<!-- #default-branch-switch -->
|
||||
|
||||
You now have a working example project.
|
||||
You can head back to the documentation, continuing browsing it from the [templates](https://mui.com/material-ui/getting-started/templates/) section.
|
19801
react_frontend/package-lock.json
generated
Normal file
19801
react_frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
react_frontend/package.json
Normal file
34
react_frontend/package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "material-cra",
|
||||
"version": "5.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "latest",
|
||||
"@emotion/styled": "latest",
|
||||
"@mui/icons-material": "^5.11.11",
|
||||
"@mui/material": "latest",
|
||||
"install": "^0.13.0",
|
||||
"npm": "^9.6.2",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-scripts": "latest"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
44
react_frontend/public/index.html
Normal file
44
react_frontend/public/index.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
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" />
|
||||
<!--
|
||||
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.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
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>Публикатор 1с</title>
|
||||
<!-- Fonts to support Material Design -->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
||||
</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.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
15
react_frontend/public/manifest.json
Normal file
15
react_frontend/public/manifest.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "Your Orders",
|
||||
"name": "Your Orders",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
95
react_frontend/src/AddPublicationForm.js
Normal file
95
react_frontend/src/AddPublicationForm.js
Normal file
@ -0,0 +1,95 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField } from '@mui/material';
|
||||
|
||||
const AddPublicationForm = ({ open, handleClose, handleAddPublication }) => {
|
||||
const [name, setName] = useState('');
|
||||
const [title, setTitle] = useState('');
|
||||
const [nameError, setNameError] = useState(false);
|
||||
const [titleError, setTitleError] = useState(false);
|
||||
const [nameErrorText, setNameErrorText] = useState('');
|
||||
const [titleErrorText, setTitleErrorText] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
resetForm();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const resetForm = () => {
|
||||
setName('');
|
||||
setTitle('');
|
||||
setNameError(false);
|
||||
setTitleError(false);
|
||||
setNameErrorText('');
|
||||
setTitleErrorText('');
|
||||
};
|
||||
|
||||
const validateInput = () => {
|
||||
const nameValid = /^[a-zA-Z0-9-_]+$/.test(name) && name.length > 0;
|
||||
const titleValid = title.length > 0;
|
||||
|
||||
setNameError(!nameValid);
|
||||
setTitleError(!titleValid);
|
||||
|
||||
setNameErrorText(nameValid ? '' : 'Имя должно содержать только латинские символы, цифры и спецсимволы URL');
|
||||
setTitleErrorText(titleValid ? '' : 'Название не может быть пустым');
|
||||
|
||||
return nameValid && titleValid;
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (validateInput()) {
|
||||
handleAddPublication({ name, title });
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="form-dialog-title"
|
||||
>
|
||||
<DialogTitle id="form-dialog-title">Добавить публикацию</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Название публикации"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
fullWidth
|
||||
error={titleError}
|
||||
helperText={titleErrorText}
|
||||
/>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="Корневой url"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
fullWidth
|
||||
error={nameError}
|
||||
helperText={nameErrorText}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Отмена
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (handleAddPublication({ name, title })) {
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
color="primary"
|
||||
>
|
||||
Добавить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default AddPublicationForm;
|
50
react_frontend/src/App.js
Normal file
50
react_frontend/src/App.js
Normal file
@ -0,0 +1,50 @@
|
||||
import * as React from 'react';
|
||||
import Container from '@mui/material/Container';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Box from '@mui/material/Box';
|
||||
import Link from '@mui/material/Link';
|
||||
import AppBarComponent from './AppBarComponent';
|
||||
import Content from './Content';
|
||||
import { DataProvider } from './DataContext';
|
||||
|
||||
function Copyright() {
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary" align="center">
|
||||
{'Copyright © '}
|
||||
<Link color="inherit" href="https://1cdevelopers.ru/">
|
||||
1cDevelopers.ru
|
||||
</Link>{' '}
|
||||
{new Date().getFullYear()}
|
||||
{'.'}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const [mobileOpen, setMobileOpen] = React.useState(false);
|
||||
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setMobileOpen(!mobileOpen);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<DataProvider>
|
||||
|
||||
<AppBarComponent handleDrawerToggle={handleDrawerToggle} />
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: '99vh',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Content handleDrawerToggle={handleDrawerToggle} mobileOpen={mobileOpen} />
|
||||
</Box>
|
||||
<Copyright />
|
||||
</Box>
|
||||
</DataProvider>
|
||||
);
|
||||
}
|
233
react_frontend/src/AppBarComponent.js
Normal file
233
react_frontend/src/AppBarComponent.js
Normal file
@ -0,0 +1,233 @@
|
||||
import * as React from 'react';
|
||||
import { createTheme, ThemeProvider } from '@mui/material/styles';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import BuildIcon from '@mui/icons-material/Build';
|
||||
import RestartAltIcon from '@mui/icons-material/RestartAlt';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import StopIcon from '@mui/icons-material/Stop';
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Typography,
|
||||
IconButton,
|
||||
Button,
|
||||
Menu,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Snackbar,
|
||||
Alert,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogContentText,
|
||||
} from '@mui/material';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import { useBasesData } from './DataContext';
|
||||
import { restartServer, stopServer, startServer, saveToServer } from './serverManagement';
|
||||
import ProjectInfoForm from './ProjectInfoForm';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
|
||||
const darkTheme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
primary: {
|
||||
main: '#4caf50',
|
||||
},
|
||||
secondary: {
|
||||
main: '#f44336',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default function AppBarComponent(props) {
|
||||
const { handleDrawerToggle } = props;
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
|
||||
const [snackbarOpen, setSnackbarOpen] = React.useState(false);
|
||||
const [snackbarMessage, setSnackbarMessage] = React.useState('');
|
||||
const [operationResult, setOperationResult] = React.useState({ success: true, message: '' });
|
||||
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const { refreshData } = useBasesData();
|
||||
|
||||
const [projectInfoFormOpen, setProjectInfoFormOpen] = React.useState(false);
|
||||
|
||||
const handleProjectInfoFormOpen = () => {
|
||||
setProjectInfoFormOpen(true);
|
||||
};
|
||||
|
||||
const handleProjectInfoFormClose = () => {
|
||||
setProjectInfoFormOpen(false);
|
||||
};
|
||||
|
||||
|
||||
const handleRefresh = async () => {
|
||||
if (window.confirm('Вы действительно хотите обновить данные из базы?')) {
|
||||
await refreshData();
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestart = async () => {
|
||||
const result = await restartServer();
|
||||
setOperationResult(result);
|
||||
setSnackbarOpen(true);
|
||||
};
|
||||
|
||||
const handleStop = async () => {
|
||||
const result = await stopServer();
|
||||
setOperationResult(result);
|
||||
setSnackbarOpen(true);
|
||||
};
|
||||
|
||||
const handleStart = async () => {
|
||||
const result = await startServer();
|
||||
setOperationResult(result);
|
||||
setSnackbarOpen(true);
|
||||
};
|
||||
|
||||
const handleSnackbarClose = (event, reason) => {
|
||||
if (reason === 'clickaway') {
|
||||
return;
|
||||
}
|
||||
setSnackbarOpen(false);
|
||||
};
|
||||
|
||||
const [saveDialogOpen, setSaveDialogOpen] = React.useState(false);
|
||||
|
||||
const handleSaveDialogOpen = () => {
|
||||
setSaveDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleSaveDialogClose = () => {
|
||||
setSaveDialogOpen(false);
|
||||
};
|
||||
|
||||
const { jsonData } = useBasesData();
|
||||
|
||||
const handleSaveAndClose = async () => {
|
||||
const result = await saveToServer(jsonData);
|
||||
setOperationResult(result);
|
||||
setSnackbarOpen(true);
|
||||
if (result.success) {
|
||||
setSaveDialogOpen(false); // закрыть диалог, если операция выполнена успешно
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
handleSaveDialogOpen();
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<AppBar position="fixed" sx={{ height: "64px", zIndex: (theme) => theme.zIndex.drawer + 1 }}>
|
||||
<Toolbar sx={{ color: 'inherit' }}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
edge="start"
|
||||
onClick={handleDrawerToggle}
|
||||
sx={{ mr: 2, display: { xs: "block", sm: "none" }, }}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<img src="/logo.png" alt="Logo" style={{ maxHeight: '40px', marginRight: '8px' }} />
|
||||
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
|
||||
Публикатор 1с.
|
||||
</Typography>
|
||||
<Button color="inherit" onClick={handleRefresh}>
|
||||
<RefreshIcon sx={{ mr: 1 }} />
|
||||
<Typography component="span" sx={{ display: { xs: "none", lg: "inline" } }}>
|
||||
Обновить из базы
|
||||
</Typography>
|
||||
</Button>
|
||||
<Button color="inherit" onClick={handleSave}>
|
||||
<SaveIcon sx={{ mr: 1 }} />
|
||||
<Typography component="span" sx={{ display: { xs: "none", lg: "inline" } }}>
|
||||
Сохранить
|
||||
</Typography>
|
||||
</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleClick}>
|
||||
<BuildIcon sx={{ mr: 1 }} />
|
||||
<Typography component="span" sx={{ display: { xs: "none", lg: "inline" } }}>
|
||||
Управление Веб-сервером
|
||||
</Typography>
|
||||
</Button>
|
||||
<Menu
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<MenuItem onClick={handleRestart}>
|
||||
<ListItemIcon>
|
||||
<RestartAltIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Перезапустить</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleStart}>
|
||||
<ListItemIcon>
|
||||
<PlayArrowIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Запустить</ListItemText>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleStop}>
|
||||
<ListItemIcon>
|
||||
<StopIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText>Остановить</ListItemText>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Button color="inherit" onClick={handleProjectInfoFormOpen}>
|
||||
<InfoIcon sx={{ mr: 1 }} />
|
||||
<Typography component="span" sx={{ display: { xs: "none", lg: "inline" } }}>
|
||||
Информация о проекте
|
||||
</Typography>
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Snackbar
|
||||
open={snackbarOpen}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleSnackbarClose}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
>
|
||||
<Alert onClose={handleSnackbarClose} severity={operationResult.success ? 'success' : 'error'} sx={{ width: '100%' }}>
|
||||
{operationResult.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<ProjectInfoForm open={projectInfoFormOpen} onClose={handleProjectInfoFormClose} />
|
||||
<Dialog
|
||||
open={saveDialogOpen}
|
||||
onClose={handleSaveDialogClose}
|
||||
>
|
||||
<DialogTitle>
|
||||
Сохранить данные на сервере
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Вы действительно хотите сохранить данные на сервере? Это перезапишет текущую конфигурацию.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleSaveDialogClose}>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button onClick={handleSaveAndClose} color="primary">
|
||||
Сохранить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</ThemeProvider>
|
||||
|
||||
);
|
||||
}
|
368
react_frontend/src/Bases.js
Normal file
368
react_frontend/src/Bases.js
Normal file
@ -0,0 +1,368 @@
|
||||
import * as React from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import List from '@mui/material/List';
|
||||
import ListItem from '@mui/material/ListItem';
|
||||
import ListItemButton from '@mui/material/ListItemButton';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import ListSubheader from '@mui/material/ListSubheader';
|
||||
import Button from '@mui/material/Button';
|
||||
import Fab from '@mui/material/Fab';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogActions from '@mui/material/DialogActions';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Box from '@mui/material/Box';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import { useBasesData } from './DataContext';
|
||||
|
||||
const drawerWidth = 360;
|
||||
|
||||
export default function BasesOptions(props) {
|
||||
const { window } = props;
|
||||
const { handleDrawerToggle, mobileOpen } = props;
|
||||
const { basesData, setBasesData } = useBasesData();
|
||||
const [selectedIndex, setSelectedIndex] = React.useState(1);
|
||||
const handleListItemClick = (event, index) => {
|
||||
setSelectedIndex(index);
|
||||
props.setSelectedIndex(index);
|
||||
};
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [editingItemIndex, setEditingItemIndex] = React.useState(-1);
|
||||
const [editedItem, setEditedItem] = React.useState(null);
|
||||
const handleClickOpen = (index) => {
|
||||
setEditingItemIndex(index);
|
||||
setEditedItem(basesData[index]);
|
||||
setOpen(true);
|
||||
};
|
||||
React.useEffect(() => {
|
||||
if (basesData.length === 0) {
|
||||
setSelectedIndex(null);
|
||||
}
|
||||
}, [basesData]);
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const errorMessage = validate(editedItem.name, editedItem.Srvr, editedItem.Ref, editingItemIndex);
|
||||
|
||||
if (errorMessage) {
|
||||
alert(errorMessage);
|
||||
return;
|
||||
}
|
||||
const newBasesData = [...basesData];
|
||||
newBasesData[editingItemIndex] = editedItem;
|
||||
setBasesData(newBasesData);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const validate = (name, Srvr, Ref, currentIndex) => {
|
||||
if (!name.trim() || !Srvr.trim() || !Ref.trim()) {
|
||||
return 'Все поля должны быть заполнены';
|
||||
}
|
||||
|
||||
if (!isUnique(name, Srvr, Ref, currentIndex)) {
|
||||
return 'База с таким наименованием, или парой "Сервер-База на сервере" уже есть';
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
const isUnique = (name, Srvr, Ref, currentIndex = -1) => {
|
||||
let uniqueName = true;
|
||||
let uniqueSrvrRef = true;
|
||||
|
||||
const lowerName = name.toLowerCase();
|
||||
const lowerSrvr = Srvr.toLowerCase();
|
||||
const lowerRef = Ref.toLowerCase();
|
||||
basesData.forEach((base, index) => {
|
||||
if (index === currentIndex) {
|
||||
return;
|
||||
}
|
||||
if (base.name.toLowerCase() === lowerName) {
|
||||
uniqueName = false;
|
||||
}
|
||||
|
||||
if (base.Srvr.toLowerCase() === lowerSrvr && base.Ref.toLowerCase() === lowerRef) {
|
||||
uniqueSrvrRef = false;
|
||||
}
|
||||
});
|
||||
return uniqueName && uniqueSrvrRef;
|
||||
};
|
||||
|
||||
const [addingNewItem, setAddingNewItem] = React.useState(false);
|
||||
const handleAddNewItem = () => {
|
||||
setEditingItemIndex(-1);
|
||||
setEditedItem({ name: "", Srvr: "", Ref: "", active: false });
|
||||
setAddingNewItem(true);
|
||||
};
|
||||
const handleChange = (event, field) => {
|
||||
setEditedItem({ ...editedItem, [field]: event.target.value });
|
||||
};
|
||||
const handleAddClose = () => {
|
||||
setAddingNewItem(false);
|
||||
};
|
||||
const handleAddSave = () => {
|
||||
const errorMessage = validate(editedItem.name, editedItem.Srvr, editedItem.Ref);
|
||||
|
||||
if (errorMessage) {
|
||||
alert(errorMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const newBase = {
|
||||
name: editedItem.name,
|
||||
Srvr: editedItem.Srvr,
|
||||
Ref: editedItem.Ref,
|
||||
active: false,
|
||||
publications: [
|
||||
{
|
||||
name: editedItem.Ref,
|
||||
title: editedItem.name,
|
||||
enable: true,
|
||||
activated: false,
|
||||
ws: {
|
||||
publishExtensionsByDefault: true
|
||||
},
|
||||
httpServices: {
|
||||
publishExtensionsByDefault: true,
|
||||
publishByDefault: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
setBasesData([...basesData, newBase]);
|
||||
setAddingNewItem(false);
|
||||
};
|
||||
|
||||
const [drawerOpen, setDrawerOpen] = React.useState(false);
|
||||
|
||||
const toggleDrawer = (open) => (event) => {
|
||||
if (
|
||||
event.type === 'keydown' &&
|
||||
(event.key === 'Tab' || event.key === 'Shift')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setDrawerOpen(open);
|
||||
};
|
||||
const drawerList = (
|
||||
<List
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
maxWidth: 360,
|
||||
bgcolor: 'background.paper',
|
||||
paddingTop: '64px',
|
||||
}}
|
||||
subheader={<ListSubheader>Список баз</ListSubheader>}
|
||||
>
|
||||
{basesData.map((value, index) => {
|
||||
const labelId = `checkbox-list-label-${value.name}`;
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={value.name}
|
||||
secondaryAction={
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="edit"
|
||||
onClick={() => handleClickOpen(index)}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
}
|
||||
disablePadding
|
||||
>
|
||||
<ListItemButton
|
||||
selected={selectedIndex === index}
|
||||
onClick={(event) =>
|
||||
handleListItemClick(event, index)
|
||||
}
|
||||
>
|
||||
<ListItemText id={labelId} primary={value.name} />
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
{basesData.length === 0 && (
|
||||
<ListItem>
|
||||
<ListItemText primary="Нет доступных баз данных" />
|
||||
</ListItem>
|
||||
)}
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label="add"
|
||||
onClick={handleAddNewItem}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 16,
|
||||
right: 16,
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
</List>
|
||||
);
|
||||
const container = window !== undefined ? () => window().document.body : undefined;
|
||||
const handleDeleteClick = () => {
|
||||
setOpenDeleteConfirmation(true);
|
||||
}
|
||||
const [openDeleteConfirmation, setOpenDeleteConfirmation] = React.useState(false);
|
||||
const handleDeleteConfirmationClose = (deleteConfirmed) => {
|
||||
if (deleteConfirmed) {
|
||||
const newBasesData = [...basesData];
|
||||
newBasesData.splice(editingItemIndex, 1);
|
||||
setBasesData(newBasesData);
|
||||
setOpenDeleteConfirmation(false);
|
||||
handleClose(); // Закрыть диалоговое окно после успешного удаления
|
||||
} else {
|
||||
setOpenDeleteConfirmation(false);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<CssBaseline />
|
||||
<Box component="main" sx={{ flexGrow: 1, paddingTop: "64px" }}>
|
||||
<Drawer
|
||||
container={container}
|
||||
variant="temporary"
|
||||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
ModalProps={{
|
||||
keepMounted: true, // Better open performance on mobile.
|
||||
}}
|
||||
sx={{
|
||||
display: { xs: "block", sm: "none" },
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box' },
|
||||
}}
|
||||
>
|
||||
{drawerList}
|
||||
</Drawer>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: { xs: "none", sm: "block" },
|
||||
width: drawerWidth,
|
||||
flexShrink: 0,
|
||||
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: 'border-box' },
|
||||
}}
|
||||
open
|
||||
>
|
||||
{drawerList}
|
||||
</Drawer>
|
||||
<Box component="section" sx={{ width: "100%", marginLeft: { sm: `${drawerWidth}px` }, padding: "16px", }}>
|
||||
<Dialog open={open} onClose={handleClose}>
|
||||
<DialogTitle>EditItem</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Внесите изменения в информацию о базе данных
|
||||
</DialogContentText>
|
||||
{editedItem && (
|
||||
<>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="Название базы"
|
||||
type="text"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={editedItem.name}
|
||||
onChange={(e) => handleChange(e, 'name')}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Сервер"
|
||||
type="text"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={editedItem.Srvr}
|
||||
onChange={(e) => handleChange(e, 'Srvr')}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Имя базы на сервере"
|
||||
type="text"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={editedItem.Ref}
|
||||
onChange={(e) => handleChange(e, 'Ref')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleDeleteClick}>Удалить базу</Button>
|
||||
<Button onClick={handleClose}>Отменить</Button>
|
||||
<Button onClick={handleSave}>Сохранить</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Dialog open={addingNewItem} onClose={handleAddClose}>
|
||||
<DialogTitle>Add New Base</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Заполните информацию о новой базе
|
||||
</DialogContentText>
|
||||
{editedItem && (
|
||||
<>
|
||||
<TextField
|
||||
autoFocus
|
||||
margin="dense"
|
||||
label="Название базы"
|
||||
type="text"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={editedItem.name}
|
||||
onChange={(e) => handleChange(e, 'name')}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Сервер"
|
||||
type="text"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={editedItem.Srvr}
|
||||
onChange={(e) => handleChange(e, 'Srvr')}
|
||||
/>
|
||||
<TextField
|
||||
margin="dense"
|
||||
label="Имя базы на сервере"
|
||||
type="text"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={editedItem.Ref}
|
||||
onChange={(e) => handleChange(e, 'Ref')}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleAddClose}>Отменить</Button>
|
||||
<Button onClick={handleAddSave}>Сохранить</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Dialog open={openDeleteConfirmation} onClose={() => handleDeleteConfirmationClose(false)}>
|
||||
<DialogTitle>Delete Base</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Уверены, что хотите удалить базу?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => handleDeleteConfirmationClose(false)}>Отмена</Button>
|
||||
<Button onClick={() => handleDeleteConfirmationClose(true)}>Удалить базу</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
</Box>
|
||||
</div >
|
||||
);
|
||||
}
|
39
react_frontend/src/Content.js
Normal file
39
react_frontend/src/Content.js
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import Bases from './Bases';
|
||||
import Publications from './Publications';
|
||||
import ExportJson from './ExportJson';
|
||||
import { useBasesData } from './DataContext';
|
||||
import styles from './css/Content.module.css';
|
||||
import SelectedIndexContext from './SelectedIndexContext';
|
||||
|
||||
function Content(props) {
|
||||
const { jsonData, basesData } = useBasesData();
|
||||
const { handleDrawerToggle, mobileOpen } = props;
|
||||
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
||||
|
||||
if (!basesData) {
|
||||
return <div>Загружается...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.container}>
|
||||
<Bases handleDrawerToggle={handleDrawerToggle} mobileOpen={mobileOpen} setSelectedIndex={setSelectedIndex} />
|
||||
<div className={styles.innerContainer}>
|
||||
<SelectedIndexContext.Provider value={selectedIndex}>
|
||||
<div className={styles.publicationsContainer}>
|
||||
<Publications />
|
||||
</div>
|
||||
</SelectedIndexContext.Provider>
|
||||
<div className={styles.exportJsonContainer}>
|
||||
<div className={styles.exportJsonButton}>
|
||||
{/* <ExportJson data={jsonData} /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Content;
|
53
react_frontend/src/DataContext.js
Normal file
53
react_frontend/src/DataContext.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { createContext, useContext, useState, useEffect } from 'react';
|
||||
|
||||
const DataContext = createContext();
|
||||
|
||||
export const useBasesData = () => {
|
||||
return useContext(DataContext);
|
||||
};
|
||||
|
||||
export const DataProvider = ({ children }) => {
|
||||
const [jsonData, setJsonData] = useState({ bases: [] });
|
||||
const [basesData, setBasesData] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const response = await fetch('https://1cdevelopers.ru/api/v1/getconfig.json');
|
||||
const strResponce = await response.text();
|
||||
const data = JSON.parse(strResponce);
|
||||
setJsonData(data);
|
||||
setBasesData(data.bases);
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setJsonData((prevJsonData) => ({
|
||||
...prevJsonData,
|
||||
bases: basesData,
|
||||
}));
|
||||
}, [basesData]);
|
||||
|
||||
const refreshData = async () => {
|
||||
try {
|
||||
const response = await fetch('https://1cdevelopers.ru/api/v1/getconfig.json');
|
||||
const strResponce = await response.text();
|
||||
const data = JSON.parse(strResponce);
|
||||
setJsonData(data);
|
||||
setBasesData(data.bases);
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DataContext.Provider value={{ basesData, setBasesData, jsonData, refreshData }}>
|
||||
{children}
|
||||
</DataContext.Provider>
|
||||
);
|
||||
};
|
128
react_frontend/src/EditPublicationForm.js
Normal file
128
react_frontend/src/EditPublicationForm.js
Normal file
@ -0,0 +1,128 @@
|
||||
// EditPublicationForm.js
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
DialogContentText,
|
||||
Button,
|
||||
Tab,
|
||||
Tabs,
|
||||
TextField,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Grid,
|
||||
} from '@mui/material';
|
||||
|
||||
import BasicParametersTab from './ediPublicationFormTabs/BasicParametersTab';
|
||||
import HttpServicesTab from './ediPublicationFormTabs/HttpServicesTab';
|
||||
import WebServicesTab from './ediPublicationFormTabs/WebServicesTab';
|
||||
import OpenIDConnectSettingsTab from './ediPublicationFormTabs/OpenIDConnectSettingsTab';
|
||||
|
||||
function EditPublicationForm({ open, handleClose, publicationIndex, publication, handleUpdatePublication, handleDeletePublication}) {
|
||||
const [selectedTab, setSelectedTab] = React.useState(0);
|
||||
const [editedPublication, setEditedPublication] = useState({ ...publication });
|
||||
const [publications, setPublications] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setEditedPublication({ ...publication });
|
||||
}, [publication]);
|
||||
|
||||
const handleSave = () => {
|
||||
handleUpdatePublication(editedPublication);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setSelectedTab(newValue);
|
||||
};
|
||||
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
setEditedPublication({
|
||||
...editedPublication,
|
||||
[event.target.name]: event.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSwitchChange = (event) => {
|
||||
setEditedPublication({
|
||||
...editedPublication,
|
||||
[event.target.name]: event.target.checked,
|
||||
});
|
||||
};
|
||||
|
||||
const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
|
||||
|
||||
const onDeletePublication = () => {
|
||||
// вызовите переданную функцию handleDeletePublication с id текущей публикации
|
||||
handleDeletePublication(publication);
|
||||
// Закройте диалог подтверждения и основной диалог
|
||||
setConfirmationDialogOpen(false);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleConfirmationDialogOpen = () => {
|
||||
setConfirmationDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleConfirmationDialogClose = () => {
|
||||
setConfirmationDialogOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
||||
<DialogTitle>Изменить публикацию</DialogTitle>
|
||||
<DialogContent>
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
onChange={handleChange}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
variant="fullWidth"
|
||||
>
|
||||
<Tab label="Основные параметры" />
|
||||
<Tab label="HTTP-сервисы" />
|
||||
<Tab label="Web-сервисы" />
|
||||
<Tab label="OpenID Connect Settings" />
|
||||
</Tabs>
|
||||
{selectedTab === 0 && (<BasicParametersTab editedPublication={editedPublication} handleInputChange={handleInputChange} handleSwitchChange={handleSwitchChange} />)}
|
||||
{selectedTab === 1 && <HttpServicesTab editedPublication={editedPublication} setEditedPublication={setEditedPublication} />}
|
||||
{selectedTab === 2 && <WebServicesTab editedPublication={editedPublication} setEditedPublication={setEditedPublication} />}
|
||||
{selectedTab === 3 && <OpenIDConnectSettingsTab editedPublication={editedPublication} setEditedPublication={setEditedPublication} />}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleConfirmationDialogOpen} color="secondary">
|
||||
Удалить публикацию
|
||||
</Button>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Отменить
|
||||
</Button>
|
||||
<Button onClick={handleSave} color="primary">
|
||||
Сохранить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Dialog open={confirmationDialogOpen}onClose={handleConfirmationDialogClose}>
|
||||
<DialogTitle>Удалить публикацию</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Вы уверены, что хотите удалить эту публикацию? Это действие нельзя будет отменить.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleConfirmationDialogClose} color="primary">
|
||||
Отменить
|
||||
</Button>
|
||||
<Button onClick={onDeletePublication} color="secondary">
|
||||
Удалить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditPublicationForm;
|
20
react_frontend/src/ExportJson.js
Normal file
20
react_frontend/src/ExportJson.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
export default function ExportButton({ data }) {
|
||||
const exportToJson = () => {
|
||||
const dataStr = JSON.stringify(data, null, 2);
|
||||
const dataUri =
|
||||
'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
|
||||
const link = document.createElement('a');
|
||||
link.href = dataUri;
|
||||
link.download = 'bases_data.json';
|
||||
link.click();
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={exportToJson} variant="contained" color="primary">
|
||||
Export to JSON
|
||||
</Button>
|
||||
);
|
||||
}
|
43
react_frontend/src/ProjectInfoForm.js
Normal file
43
react_frontend/src/ProjectInfoForm.js
Normal file
@ -0,0 +1,43 @@
|
||||
import * as React from 'react';
|
||||
import { Typography, Button, Dialog, DialogTitle, DialogContent, DialogActions, Box, Link } from '@mui/material';
|
||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
|
||||
|
||||
function ProjectInfoForm({ open, onClose }) {
|
||||
const projectName = 'Публикатор 1С';
|
||||
const projectVersion = '0.8.2';
|
||||
const projectDescription = 'Проект преднозначен для удобной работы с публикациями на Веб-сервере. Код полностью открыт и доступен! \n Присоединяйтесь в катанл в телеграм, чтобы не пропутсить критичные обновления.';
|
||||
const telegramUrl = 'https://t.me/DevOps_onec';
|
||||
const githubUrl = 'https://github.com/Segate-ekb/publicator';
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Информация о проекте</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box display="flex" alignItems="center" justifyContent="center" mb={2}>
|
||||
<img src="/logo.png" alt="Logo" style={{ maxHeight: '80px', marginRight: '8px' }} />
|
||||
<Typography variant="h5">{projectName}</Typography>
|
||||
</Box>
|
||||
<Typography variant="subtitle1">Версия: {projectVersion}</Typography>
|
||||
<Typography variant="body1" mt={2}>{projectDescription}</Typography>
|
||||
<Box mt={2}>
|
||||
<Typography variant="subtitle2">Ссылки:</Typography>
|
||||
<Box display="flex" alignItems="center">
|
||||
<Link href={telegramUrl} target="_blank" rel="noopener noreferrer" sx={{ marginRight: 2 }}>
|
||||
<TelegramIcon color="primary" fontSize="large" />
|
||||
</Link>
|
||||
<Link href={githubUrl} target="_blank" rel="noopener noreferrer">
|
||||
<GitHubIcon color="action" fontSize="large" />
|
||||
</Link>
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Ок</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectInfoForm;
|
245
react_frontend/src/Publications.js
Normal file
245
react_frontend/src/Publications.js
Normal file
@ -0,0 +1,245 @@
|
||||
// Publications.js
|
||||
import React, { useContext, useState } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardActions,
|
||||
CardContent,
|
||||
Typography,
|
||||
Switch,
|
||||
IconButton,
|
||||
Link,
|
||||
Grid,
|
||||
Breadcrumbs,
|
||||
Fab
|
||||
} from '@mui/material';
|
||||
import { useBasesData } from './DataContext';
|
||||
import SelectedIndexContext from './SelectedIndexContext';
|
||||
import styles from './css/Publications.module.css';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
|
||||
import AddPublicationForm from './AddPublicationForm';
|
||||
import EditPublicationForm from './EditPublicationForm'
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
|
||||
|
||||
function Publications() {
|
||||
const { basesData, setBasesData } = useBasesData();
|
||||
const selectedIndex = useContext(SelectedIndexContext);
|
||||
const selectedBase = basesData[selectedIndex];
|
||||
|
||||
if (!selectedBase) {
|
||||
return <div>Загружается...</div>;
|
||||
}
|
||||
const publications = selectedBase.publications;
|
||||
|
||||
const handleBreadcrumbClick = () => {
|
||||
// handle click on breadcrumb
|
||||
}
|
||||
|
||||
if (!basesData || !basesData[selectedIndex]) {
|
||||
return <div>Не выбрана активная база!</div>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const handleToggle = (index) => () => {
|
||||
const updatedPublications = publications.map((publication, i) => {
|
||||
if (i === index) {
|
||||
return { ...publication, active: !publication.active };
|
||||
}
|
||||
return publication;
|
||||
});
|
||||
|
||||
const newChecked = updatedPublications
|
||||
.filter((publication) => publication.active)
|
||||
.reduce((acc, curr) => acc || curr.active, false);
|
||||
|
||||
setBasesData((prevBasesData) => {
|
||||
const updatedBasesData = prevBasesData.map((base, i) =>
|
||||
i === selectedIndex
|
||||
? { ...base, publications: updatedPublications }
|
||||
: base
|
||||
);
|
||||
return updatedBasesData;
|
||||
});
|
||||
};
|
||||
|
||||
const [openAddForm, setOpenAddForm] = useState(false);
|
||||
const [openEditForm, setOpenEditForm] = useState(false);
|
||||
const [selectedPublicationIndex, setSelectedPublicationIndex] = useState(null);
|
||||
|
||||
const handleOpenAddForm = () => {
|
||||
setOpenAddForm(true);
|
||||
};
|
||||
|
||||
const handleCloseAddForm = () => {
|
||||
setOpenAddForm(false);
|
||||
};
|
||||
|
||||
const handleAddPublication = ({ name, title }, closeForm )=> {
|
||||
|
||||
if (!isUnique(name, title)) {
|
||||
alert("Name и Title должны быть уникальными в пределах базы данных.");
|
||||
return false;
|
||||
}
|
||||
const newPublication = {
|
||||
name,
|
||||
title,
|
||||
actived: true,
|
||||
enable: true,
|
||||
ws: {
|
||||
publishExtensionsByDefault: true,
|
||||
wsList: [],
|
||||
},
|
||||
httpServices: {
|
||||
publishExtensionsByDefault: true,
|
||||
publishByDefault: true,
|
||||
hsList: [],
|
||||
},
|
||||
};
|
||||
|
||||
setBasesData((prevBasesData) => {
|
||||
const newPublications = [...prevBasesData[selectedIndex].publications, newPublication];
|
||||
const newBasesData = prevBasesData.map((base, index) =>
|
||||
index === selectedIndex ? { ...base, publications: newPublications } : base
|
||||
);
|
||||
return newBasesData;
|
||||
});
|
||||
|
||||
closeForm();
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleUpdatePublication = (index, updatedPublication, closeForm) => {
|
||||
|
||||
if (!isUnique(updatedPublication.name, updatedPublication.title, index)) {
|
||||
alert("Name и Title должны быть уникальными в пределах базы данных.");
|
||||
return false;
|
||||
}
|
||||
setBasesData((prevBasesData) => {
|
||||
const newPublications = prevBasesData[selectedIndex].publications.map((publication, i) =>
|
||||
i === index ? updatedPublication : publication
|
||||
);
|
||||
|
||||
const newBasesData = prevBasesData.map((base, i) =>
|
||||
i === selectedIndex ? { ...base, publications: newPublications } : base
|
||||
);
|
||||
|
||||
return newBasesData;
|
||||
});
|
||||
closeForm();
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
const handleOpenEditForm = (index) => {
|
||||
setSelectedPublicationIndex(index);
|
||||
setOpenEditForm(true);
|
||||
};
|
||||
|
||||
const handleCloseEditForm = () => {
|
||||
setOpenEditForm(false);
|
||||
};
|
||||
|
||||
const handleDeletePublication = (publication_income) => {
|
||||
// Удалите публикацию с заданным id из массива publications
|
||||
console.log(publication_income);
|
||||
publications.forEach((publication) => {
|
||||
console.log (publication.name == publication_income.name);
|
||||
console.log (publication);
|
||||
})
|
||||
const updatedPublications = publications.filter((publication) => publication.name != publication_income.name);
|
||||
console.log(updatedPublications);
|
||||
|
||||
// Обновите состояние basesData с помощью setBasesData
|
||||
setBasesData((prevBasesData) => {
|
||||
const updatedBasesData = prevBasesData.map((base, i) =>
|
||||
i === selectedIndex
|
||||
? { ...base, publications: updatedPublications }
|
||||
: base
|
||||
);
|
||||
return updatedBasesData;
|
||||
});
|
||||
};
|
||||
|
||||
const isUnique = (name, title, ignoreIndex) => {
|
||||
return !publications.some((pub, index) => {
|
||||
if (index === ignoreIndex) {
|
||||
return false;
|
||||
}
|
||||
return pub.name === name || pub.title === title;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.publicationsWrapper}>
|
||||
<div className={styles.publicationsContainer}>
|
||||
<Breadcrumbs
|
||||
separator={<NavigateNextIcon fontSize="small" />}
|
||||
aria-label="breadcrumb"
|
||||
style={{ marginLeft: '16px' }}
|
||||
>
|
||||
<Typography color="textPrimary">{selectedBase.name}</Typography>
|
||||
|
||||
<Typography color="textPrimary">Публикации</Typography>
|
||||
</Breadcrumbs>
|
||||
<Typography color="textSecondary">
|
||||
Srvr: {selectedBase.Srvr} | Ref: {selectedBase.Ref}
|
||||
</Typography>
|
||||
<Grid container spacing={3}>
|
||||
{publications.map((publication, index) => (
|
||||
<Grid item xs={12} sm={4} md={3} key={index}>
|
||||
<Card key={index} className={`${styles.publicationCard} ${styles.fixedSizeCard}`}>
|
||||
<CardContent>
|
||||
<Typography variant="h5">{publication.title}</Typography>
|
||||
<Typography
|
||||
color="text.secondary"
|
||||
display="block"
|
||||
variant="caption"
|
||||
>{publication.name}</Typography>
|
||||
</CardContent>
|
||||
<CardActions style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<Switch
|
||||
checked={publication.active}
|
||||
onChange={handleToggle(index)}
|
||||
inputProps={{ 'aria-label': 'Toggle publication' }}
|
||||
/>
|
||||
<IconButton onClick={() => handleOpenEditForm(index)} color="primary" style={{ marginLeft: '8px' }}>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
<Fab
|
||||
color="primary"
|
||||
onClick={handleOpenAddForm}
|
||||
className={styles.addPublicationButton}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
<AddPublicationForm
|
||||
open={openAddForm}
|
||||
handleClose={handleCloseAddForm}
|
||||
handleAddPublication={({ name, title }) => handleAddPublication({ name, title }, handleCloseAddForm)}
|
||||
/>
|
||||
|
||||
<EditPublicationForm
|
||||
open={openEditForm}
|
||||
handleClose={handleCloseEditForm}
|
||||
publicationIndex={selectedPublicationIndex}
|
||||
publication={publications[selectedPublicationIndex]}
|
||||
handleDeletePublication={handleDeletePublication}
|
||||
handleUpdatePublication={(updatedPublication) =>
|
||||
handleUpdatePublication(selectedPublicationIndex, updatedPublication, handleCloseEditForm)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Publications;
|
5
react_frontend/src/SelectedIndexContext.js
Normal file
5
react_frontend/src/SelectedIndexContext.js
Normal file
@ -0,0 +1,5 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
const SelectedIndexContext = createContext(null);
|
||||
|
||||
export default SelectedIndexContext;
|
29
react_frontend/src/css/Content.module.css
Normal file
29
react_frontend/src/css/Content.module.css
Normal file
@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding-top: 64px;
|
||||
align-items: left;
|
||||
}
|
||||
|
||||
|
||||
.innerContainer {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
.publicationsContainer {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.exportJsonContainer {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.exportJsonButton {
|
||||
margin-top: auto;
|
||||
}
|
20
react_frontend/src/css/Publications.module.css
Normal file
20
react_frontend/src/css/Publications.module.css
Normal file
@ -0,0 +1,20 @@
|
||||
.publicationsWrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.publicationsContainer {
|
||||
flex-grow: 1;
|
||||
margin-bottom: 40px; /* или другое значение в зависимости от размера кнопки */
|
||||
}
|
||||
|
||||
.addPublicationButton {
|
||||
margin-bottom: -80px;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.publicationCard {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
}
|
19
react_frontend/src/darkTheme.js
Normal file
19
react_frontend/src/darkTheme.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { red } from '@mui/material/colors';
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
|
||||
// A custom theme for this app
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#556cd6',
|
||||
},
|
||||
secondary: {
|
||||
main: '#19857b',
|
||||
},
|
||||
error: {
|
||||
main: red.A400,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default theme;
|
89
react_frontend/src/ediPublicationFormTabs/AddHsItemDialog.js
Normal file
89
react_frontend/src/ediPublicationFormTabs/AddHsItemDialog.js
Normal file
@ -0,0 +1,89 @@
|
||||
// AddHsItemDialog.js
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
TextField,
|
||||
Grid,
|
||||
} from '@mui/material';
|
||||
|
||||
function AddHsItemDialog({ open, handleClose, handleAddHsItem }) {
|
||||
const [name, setName] = useState('');
|
||||
const [rootUrl, setRootUrl] = useState('');
|
||||
|
||||
const handleSave = () => {
|
||||
handleAddHsItem({
|
||||
name,
|
||||
rootUrl,
|
||||
enable: true,
|
||||
reuseSessions: 'dontuse',
|
||||
sessionMaxAge: 20,
|
||||
poolSize: 10,
|
||||
poolTimeout: 5,
|
||||
});
|
||||
setName('');
|
||||
setRootUrl('');
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleChange = (event, setter) => {
|
||||
setter(event.target.value);
|
||||
};
|
||||
|
||||
const isValidName = () => {
|
||||
const regex = /^[a-zA-Z0-9-_]+$/;
|
||||
return regex.test(name);
|
||||
};
|
||||
|
||||
const isValidRootUrl = () => {
|
||||
const regex = /^[a-zA-Z0-9-_.:/]+$/;
|
||||
return regex.test(rootUrl);
|
||||
};
|
||||
|
||||
const isFormValid = () => {
|
||||
return isValidName() && isValidRootUrl();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>Добавить новый элемент</DialogTitle>
|
||||
<DialogContent>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Name"
|
||||
value={name}
|
||||
onChange={(event) => handleChange(event, setName)}
|
||||
error={!isValidName() && name !== ''}
|
||||
helperText={!isValidName() && name !== '' && 'Только латинские символы, цифры, дефисы и подчеркивания.'}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Root URL"
|
||||
value={rootUrl}
|
||||
onChange={(event) => handleChange(event, setRootUrl)}
|
||||
error={!isValidRootUrl() && rootUrl !== ''}
|
||||
helperText={!isValidRootUrl() && rootUrl !== '' && 'Введите корректный URL.'}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Отменить
|
||||
</Button>
|
||||
<Button onClick={handleSave} color="primary" disabled={!isFormValid()}>
|
||||
Сохранить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddHsItemDialog;
|
89
react_frontend/src/ediPublicationFormTabs/AddWsItemDialog.js
Normal file
89
react_frontend/src/ediPublicationFormTabs/AddWsItemDialog.js
Normal file
@ -0,0 +1,89 @@
|
||||
// AddWsItemDialog.js
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
TextField,
|
||||
Grid,
|
||||
} from '@mui/material';
|
||||
|
||||
function AddWsItemDialog({ open, handleClose, handleAddWsItem }) {
|
||||
const [name, setName] = useState('');
|
||||
const [alias, setAlias] = useState('');
|
||||
|
||||
const handleSave = () => {
|
||||
handleAddWsItem({
|
||||
name,
|
||||
alias,
|
||||
enable: true,
|
||||
reuseSessions: 'dontuse',
|
||||
sessionMaxAge: 20,
|
||||
poolSize: 10,
|
||||
poolTimeout: 5,
|
||||
});
|
||||
setName('');
|
||||
setAlias('');
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleChange = (event, setter) => {
|
||||
setter(event.target.value);
|
||||
};
|
||||
|
||||
const isValidName = () => {
|
||||
const regex = /^[a-zA-Z0-9-_]+$/;
|
||||
return regex.test(name);
|
||||
};
|
||||
|
||||
const isValidAlias = () => {
|
||||
const regex = /^[a-zA-Z0-9-_.:/]+.1cws$/;
|
||||
return regex.test(alias);
|
||||
};
|
||||
|
||||
const isFormValid = () => {
|
||||
return isValidName() && isValidAlias();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="xs" fullWidth>
|
||||
<DialogTitle>Добавить новый элемент</DialogTitle>
|
||||
<DialogContent>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Name"
|
||||
value={name}
|
||||
onChange={(event) => handleChange(event, setName)}
|
||||
error={!isValidName() && name !== ''}
|
||||
helperText={!isValidName() && name !== '' && 'Только латинские символы, цифры, дефисы и подчеркивания.'}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Алиас"
|
||||
value={alias}
|
||||
onChange={(event) => handleChange(event, setAlias)}
|
||||
error={!isValidAlias() && alias !== ''}
|
||||
helperText={!isValidAlias() && alias !== '' && 'Введите корректный URL.'}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} color="primary">
|
||||
Отменить
|
||||
</Button>
|
||||
<Button onClick={handleSave} color="primary" disabled={!isFormValid()}>
|
||||
Сохранить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddWsItemDialog;
|
@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
TextField,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Grid,
|
||||
} from '@mui/material';
|
||||
import styles from './EditPublicationForm.module.css';
|
||||
|
||||
function BasicParametersTab(props) {
|
||||
return (
|
||||
<div>
|
||||
<Grid container spacing={3} className={styles.grid}>
|
||||
<Grid item xs={8}>
|
||||
<TextField
|
||||
label="Наименование подключения"
|
||||
name="title"
|
||||
value={props.editedPublication.title}
|
||||
onChange={props.handleInputChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={props.editedPublication.active}
|
||||
onChange={props.handleSwitchChange}
|
||||
name="active"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Активно ли подключение"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
label="location"
|
||||
name="name"
|
||||
value={props.editedPublication.name}
|
||||
onChange={props.handleInputChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
label="Имя пользователя"
|
||||
name="usr"
|
||||
value={props.editedPublication.usr}
|
||||
onChange={props.handleInputChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField
|
||||
label="Пароль"
|
||||
name="pwd"
|
||||
type="password"
|
||||
value={props.editedPublication.pwd}
|
||||
onChange={props.handleInputChange}
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={props.editedPublication.enable}
|
||||
onChange={props.handleSwitchChange}
|
||||
name="enable"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Разрешен ли вход пользователей"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={props.editedPublication.enableStandardOData}
|
||||
onChange={props.handleSwitchChange}
|
||||
name="enableStandardOData"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Разрешить OData"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BasicParametersTab;
|
@ -0,0 +1,5 @@
|
||||
.grid {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
293
react_frontend/src/ediPublicationFormTabs/HttpServicesTab.js
Normal file
293
react_frontend/src/ediPublicationFormTabs/HttpServicesTab.js
Normal file
@ -0,0 +1,293 @@
|
||||
// HttpServicesTab.js
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Typography,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Box,
|
||||
TextField,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
Button,
|
||||
Grid
|
||||
} from '@mui/material';
|
||||
import { ExpandMore } from '@mui/icons-material';
|
||||
import AddItemDialog from './AddHsItemDialog';
|
||||
import Fab from '@mui/material/Fab';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import Divider from '@mui/material/Divider';
|
||||
|
||||
function HttpServicesTab({ editedPublication = {
|
||||
httpServices: {
|
||||
publishExtensionsByDefault: false,
|
||||
publishByDefault: false,
|
||||
hsList: []
|
||||
}
|
||||
}, setEditedPublication }) {
|
||||
|
||||
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
||||
const handleSwitchChange = (event) => {
|
||||
setEditedPublication({
|
||||
...editedPublication,
|
||||
httpServices: {
|
||||
...editedPublication.httpServices,
|
||||
[event.target.name]: event.target.checked,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleAccordionChange = (index, event) => {
|
||||
const newHsList = editedPublication.httpServices.hsList.map((item, idx) => {
|
||||
if (idx === index) {
|
||||
return { ...item, [event.target.name]: event.target.value };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
|
||||
|
||||
setEditedPublication({
|
||||
...editedPublication,
|
||||
httpServices: { ...editedPublication.httpServices, hsList: newHsList },
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddDialogOpen = () => {
|
||||
setAddDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleAddDialogClose = () => {
|
||||
setAddDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleAddHsItem = (newHsItem) => {
|
||||
setEditedPublication((prevPublication) => {
|
||||
const prevHsList = prevPublication.httpServices?.hsList ?? [];
|
||||
return {
|
||||
...prevPublication,
|
||||
httpServices: {
|
||||
...prevPublication.httpServices,
|
||||
hsList: [...prevHsList, newHsItem],
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handleDeleteHsItem = (index) => {
|
||||
const newHsList = editedPublication.httpServices.hsList.filter(
|
||||
(_, idx) => idx !== index
|
||||
);
|
||||
|
||||
setEditedPublication({
|
||||
...editedPublication,
|
||||
httpServices: { ...editedPublication.httpServices, hsList: newHsList },
|
||||
});
|
||||
};
|
||||
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [itemToDelete, setItemToDelete] = useState(null);
|
||||
|
||||
const handleDeleteDialogOpen = (index) => {
|
||||
setItemToDelete(index);
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteDialogClose = () => {
|
||||
setItemToDelete(null);
|
||||
setDeleteDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleConfirmDelete = () => {
|
||||
handleDeleteHsItem(itemToDelete);
|
||||
handleDeleteDialogClose();
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={editedPublication.httpServices.publishExtensionsByDefault}
|
||||
onChange={handleSwitchChange}
|
||||
name="publishExtensionsByDefault"
|
||||
/>
|
||||
}
|
||||
label="Разрешить публиковать http-сервисы расширений по умолчанию"
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={editedPublication.httpServices.publishByDefault}
|
||||
onChange={handleSwitchChange}
|
||||
name="publishByDefault"
|
||||
/>
|
||||
}
|
||||
label="Разрешить публиковать http-сервисы основной конфигурации по умолчанию"
|
||||
/>
|
||||
<Divider textAlign="left">Http-Сервисы</Divider>
|
||||
|
||||
{(editedPublication.httpServices?.hsList ?? []).map((hsItem, index) => (
|
||||
<Accordion key={index}>
|
||||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||
<Typography variant="h4">{hsItem.name}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={2} sm={4}>
|
||||
<TextField
|
||||
label="Наименование"
|
||||
value={hsItem.name}
|
||||
name="name"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={4}>
|
||||
<TextField
|
||||
label="Root URL"
|
||||
value={hsItem.rootUrl}
|
||||
name="rootUrl"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={4}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={hsItem.enable}
|
||||
onChange={() =>
|
||||
handleAccordionChange(index, { target: { name: 'enable', value: !hsItem.enable } })
|
||||
}
|
||||
name="enable"
|
||||
/>
|
||||
}
|
||||
label="Использование"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={3}>
|
||||
<TextField
|
||||
select
|
||||
label="Reuse Sessions"
|
||||
value={hsItem.reuseSessions}
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
name="reuseSessions"
|
||||
SelectProps={{
|
||||
native: true,
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{
|
||||
value: 'dontuse',
|
||||
label: 'Не использовать',
|
||||
},
|
||||
{
|
||||
value: 'use',
|
||||
label: 'Использовать',
|
||||
},
|
||||
{
|
||||
value: 'autouse',
|
||||
label: 'Авто',
|
||||
}
|
||||
].map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={3}>
|
||||
<TextField
|
||||
label="Session Max Age"
|
||||
value={hsItem.sessionMaxAge}
|
||||
name="sessionMaxAge"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
type="number"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={3}>
|
||||
<TextField
|
||||
label="Pool Size"
|
||||
value={hsItem.poolSize}
|
||||
name="poolSize"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
type="number"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={3}>
|
||||
<TextField
|
||||
label="Pool Timeout"
|
||||
value={hsItem.poolTimeout}
|
||||
name="poolTimeout"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
type="number"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} textAlign="right">
|
||||
<IconButton
|
||||
edge="end"
|
||||
color="inherit"
|
||||
onClick={() => handleDeleteDialogOpen(index)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
<Fab
|
||||
color="inherit"
|
||||
aria-label="add"
|
||||
size="small"
|
||||
onClick={handleAddDialogOpen}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
top: 10,
|
||||
left: 800,
|
||||
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
<AddItemDialog
|
||||
open={addDialogOpen}
|
||||
handleClose={handleAddDialogClose}
|
||||
handleAddHsItem={handleAddHsItem}
|
||||
/>
|
||||
<Dialog
|
||||
open={deleteDialogOpen}
|
||||
onClose={handleDeleteDialogClose}
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>Подтверждение удаления</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Вы уверены, что хотите удалить данный элемент?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleDeleteDialogClose} color="primary">
|
||||
Отменить
|
||||
</Button>
|
||||
<Button onClick={handleConfirmDelete} color="primary" autoFocus>
|
||||
Удалить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default HttpServicesTab;
|
@ -0,0 +1,91 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Grid, Typography, Accordion, AccordionDetails, AccordionSummary, Tabs, Tab } from '@mui/material';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import GeneralSettingsTab from './openid_parts/GeneralSettingsTab';
|
||||
import ProviderSettingsTab from './openid_parts/ProviderSettingsTab';
|
||||
import ClientSettingsTab from './openid_parts/ClientSettingsTab';
|
||||
import Fab from '@mui/material/Fab';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import AddProviderDialog from './openid_parts/AddProviderDialog';
|
||||
|
||||
function OpenIDConnectSettingsTab({ editedPublication, handleInputChange }) {
|
||||
const [providers, setProviders] = useState(editedPublication?.oidc?.providers || []);
|
||||
const [selectedTab, setSelectedTab] = useState(0);
|
||||
const [addProviderDialogOpen, setAddProviderDialogOpen] = useState(false);
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setSelectedTab(newValue);
|
||||
};
|
||||
|
||||
const handleAddProvider = (newProvider) => {
|
||||
const newProviders = [...providers, newProvider];
|
||||
setProviders(newProviders);
|
||||
setSelectedTab(0);
|
||||
setAddProviderDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleDeleteProvider = (providerIndex) => {
|
||||
const newProviders = [...providers];
|
||||
newProviders.splice(providerIndex, 1);
|
||||
setProviders(newProviders);
|
||||
};
|
||||
|
||||
const handleProviderChange = (index, newProvider) => {
|
||||
const newProviders = [...providers];
|
||||
newProviders[index] = newProvider;
|
||||
setProviders(newProviders);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
OpenID Connect Settings
|
||||
</Typography>
|
||||
</Grid>
|
||||
{providers && providers.map((provider, index) => (
|
||||
<Grid item xs={12} key={index}>
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls={`panel${index}-content`} id={`panel${index}-header`}>
|
||||
<Typography>{provider.title}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Tabs value={selectedTab} onChange={handleChange}>
|
||||
<Tab label="General" />
|
||||
<Tab label="Provider" />
|
||||
<Tab label="Client" />
|
||||
</Tabs>
|
||||
selectedTab === 0 && <GeneralSettingsTab provider={provider} handleInputChange={handleInputChange} handleProviderChange={(newProvider) => handleProviderChange(index, newProvider)} />}
|
||||
{selectedTab === 1 && <ProviderSettingsTab provider={provider} handleInputChange={handleInputChange} handleProviderChange={(newProvider) => handleProviderChange(index, newProvider)} />}
|
||||
{selectedTab === 2 && <ClientSettingsTab provider={provider} handleInputChange={handleInputChange} handleProviderChange={(newProvider) => handleProviderChange(index, newProvider)} />}
|
||||
</AccordionDetails>
|
||||
<Divider />
|
||||
<IconButton aria-label="delete provider" onClick={() => handleDeleteProvider(index)}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Accordion>
|
||||
</Grid>
|
||||
))}
|
||||
<Grid item xs={12}>
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label="add"
|
||||
size="small"
|
||||
onClick={() => setAddProviderDialogOpen(true)}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
</Grid>
|
||||
<AddProviderDialog
|
||||
open={addProviderDialogOpen}
|
||||
handleClose={() => setAddProviderDialogOpen(false)}
|
||||
handleAddProvider={handleAddProvider}
|
||||
/>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default OpenIDConnectSettingsTab;
|
275
react_frontend/src/ediPublicationFormTabs/WebServicesTab.js
Normal file
275
react_frontend/src/ediPublicationFormTabs/WebServicesTab.js
Normal file
@ -0,0 +1,275 @@
|
||||
// WebServicesTab.js
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
Typography,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Box,
|
||||
TextField,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogActions,
|
||||
Button,
|
||||
Grid
|
||||
} from '@mui/material';
|
||||
import { ExpandMore } from '@mui/icons-material';
|
||||
import AddWsItemDialog from './AddWsItemDialog';
|
||||
import Fab from '@mui/material/Fab';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import Divider from '@mui/material/Divider';
|
||||
|
||||
function WebServicesTab({ editedPublication, setEditedPublication }) {
|
||||
|
||||
const [addDialogOpen, setAddDialogOpen] = useState(false);
|
||||
const handleSwitchChange = (event) => {
|
||||
setEditedPublication({
|
||||
...editedPublication,
|
||||
ws: {
|
||||
...editedPublication.ws,
|
||||
[event.target.name]: event.target.checked,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleAccordionChange = (index, event) => {
|
||||
const newWsList = editedPublication.ws.wsList.map((item, idx) => {
|
||||
if (idx === index) {
|
||||
return { ...item, [event.target.name]: event.target.value };
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
setEditedPublication({
|
||||
...editedPublication,
|
||||
ws: { ...editedPublication.ws, wsList: newWsList },
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddDialogOpen = () => {
|
||||
setAddDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleAddDialogClose = () => {
|
||||
setAddDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleAddWsItem = (newWsItem) => {
|
||||
setEditedPublication((prevPublication) => {
|
||||
const prevWsList = prevPublication.ws?.wsList ?? [];
|
||||
return {
|
||||
...prevPublication,
|
||||
ws: {
|
||||
...prevPublication.ws,
|
||||
wsList: [...prevWsList, newWsItem],
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteWsItem = (index) => {
|
||||
const newWsList = editedPublication.ws.wsList.filter(
|
||||
(_, idx) => idx !== index
|
||||
);
|
||||
|
||||
setEditedPublication({
|
||||
...editedPublication,
|
||||
ws: { ...editedPublication.ws, wsList: newWsList },
|
||||
});
|
||||
};
|
||||
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [itemToDelete, setItemToDelete] = useState(null);
|
||||
|
||||
const handleDeleteDialogOpen = (index) => {
|
||||
setItemToDelete(index);
|
||||
setDeleteDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteDialogClose = () => {
|
||||
setItemToDelete(null);
|
||||
setDeleteDialogOpen(false);
|
||||
};
|
||||
|
||||
const handleConfirmDelete = () => {
|
||||
handleDeleteWsItem(itemToDelete);
|
||||
handleDeleteDialogClose();
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={editedPublication.ws.publishExtensionsByDefault}
|
||||
onChange={handleSwitchChange}
|
||||
name="publishExtensionsByDefault"
|
||||
/>
|
||||
}
|
||||
label="Разрешить публиковать Web-сервисы расширений по умолчанию"
|
||||
/>
|
||||
|
||||
<Divider textAlign="left">Web-Сервисы</Divider>
|
||||
{(editedPublication.ws?.wsList ?? []).map((wsItem, index) => (
|
||||
<Accordion key={index}>
|
||||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||
<Typography variant="h4">{wsItem.name}</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={2} sm={4}>
|
||||
<TextField
|
||||
label="Наименование"
|
||||
value={wsItem.name}
|
||||
name="name"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={4}>
|
||||
<TextField
|
||||
label="Алиас"
|
||||
value={wsItem.alias}
|
||||
name="alias"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
required
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={4}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={wsItem.enable}
|
||||
onChange={() =>
|
||||
handleAccordionChange(index, { target: { name: 'enable', value: !wsItem.enable } })
|
||||
}
|
||||
name="enable"
|
||||
/>
|
||||
}
|
||||
label="Использование"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={3}>
|
||||
<TextField
|
||||
select
|
||||
label="Reuse Sessions"
|
||||
value={wsItem.reuseSessions}
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
name="reuseSessions"
|
||||
SelectProps={{
|
||||
native: true,
|
||||
}}
|
||||
>
|
||||
{[
|
||||
{
|
||||
value: 'dontuse',
|
||||
label: 'Не использовать',
|
||||
},
|
||||
{
|
||||
value: 'use',
|
||||
label: 'Использовать',
|
||||
},
|
||||
{
|
||||
value: 'autouse',
|
||||
label: 'Авто',
|
||||
}
|
||||
].map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</TextField>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={3}>
|
||||
<TextField
|
||||
label="Session Max Age"
|
||||
value={wsItem.sessionMaxAge}
|
||||
name="sessionMaxAge"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
type="number"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={3}>
|
||||
<TextField
|
||||
label="Pool Size"
|
||||
value={wsItem.poolSize}
|
||||
name="poolSize"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
type="number"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={2} sm={3}>
|
||||
<TextField
|
||||
label="Pool Timeout"
|
||||
value={wsItem.poolTimeout}
|
||||
name="poolTimeout"
|
||||
onChange={(event) => handleAccordionChange(index, event)}
|
||||
type="number"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} textAlign="right">
|
||||
<IconButton
|
||||
edge="end"
|
||||
color="inherit"
|
||||
onClick={() => handleDeleteDialogOpen(index)}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))}
|
||||
<Fab
|
||||
color="inherit"
|
||||
aria-label="add"
|
||||
size="small"
|
||||
onClick={handleAddDialogOpen}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
top: 10,
|
||||
left: 800,
|
||||
|
||||
}}
|
||||
>
|
||||
<AddIcon />
|
||||
</Fab>
|
||||
<AddWsItemDialog
|
||||
open={addDialogOpen}
|
||||
handleClose={handleAddDialogClose}
|
||||
handleAddWsItem={handleAddWsItem}
|
||||
/>
|
||||
<Dialog
|
||||
open={deleteDialogOpen}
|
||||
onClose={handleDeleteDialogClose}
|
||||
maxWidth="xs"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>Подтверждение удаления</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Вы уверены, что хотите удалить данный элемент?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
|
||||
<Button onClick={handleDeleteDialogClose} color="primary">
|
||||
Отменить
|
||||
</Button>
|
||||
<Button onClick={handleConfirmDelete} color="primary" autoFocus>
|
||||
Удалить
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default WebServicesTab;
|
@ -0,0 +1,127 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
|
||||
function AddProviderDialog({ open, handleClose, handleAddProvider, providers }) {
|
||||
const [selectedProviderIndex, setSelectedProviderIndex] = useState(0);
|
||||
const [newProvider, setNewProvider] = useState({
|
||||
name: '',
|
||||
title: '',
|
||||
authenticationClaimName: 'email',
|
||||
authenticationUserPropertyName: 'email',
|
||||
icon: '',
|
||||
});
|
||||
|
||||
const handleAdd = () => {
|
||||
handleAddProvider(newProvider);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleProviderChange = (event) => {
|
||||
setSelectedProviderIndex(event.target.value);
|
||||
};
|
||||
|
||||
const handleChange = (event) => {
|
||||
setNewProvider({
|
||||
...newProvider,
|
||||
[event.target.name]: event.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleImageUpload = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setNewProvider({ ...newProvider, icon: reader.result });
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose}>
|
||||
<DialogTitle>Add Provider</DialogTitle>
|
||||
<DialogContent>
|
||||
<form>
|
||||
<TextField
|
||||
label="Name"
|
||||
name="name"
|
||||
value={newProvider.name}
|
||||
onChange={handleChange}
|
||||
required
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Title"
|
||||
name="title"
|
||||
value={newProvider.title}
|
||||
onChange={handleChange}
|
||||
required
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
label="Authentication Claim Name"
|
||||
name="authenticationClaimName"
|
||||
value={newProvider.authenticationClaimName}
|
||||
onChange={handleChange}
|
||||
required
|
||||
fullWidth
|
||||
margin="normal"
|
||||
/>
|
||||
<FormControl fullWidth margin="normal">
|
||||
<InputLabel>Authentication User Property Name</InputLabel>
|
||||
<Select
|
||||
name="authenticationUserPropertyName"
|
||||
value={newProvider.authenticationUserPropertyName}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value="name">name</MenuItem>
|
||||
<MenuItem value="OSUser">OSUser</MenuItem>
|
||||
<MenuItem value="email">email</MenuItem>
|
||||
<MenuItem value="matchingKey">matchingKey</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Box mb={2}>
|
||||
<input
|
||||
accept="image/*"
|
||||
style={{ display: 'none' }}
|
||||
id="icon-upload"
|
||||
type="file"
|
||||
onChange={handleImageUpload}
|
||||
/>
|
||||
<label htmlFor="icon-upload">
|
||||
<Button variant="contained" component="span">
|
||||
Upload Icon
|
||||
</Button>
|
||||
</label>
|
||||
</Box>
|
||||
{newProvider.icon && (
|
||||
<Box mb={2}>
|
||||
<img src={newProvider.icon} alt="Provider Icon" style={{ maxHeight: '100px', maxWidth: '100px' }} />
|
||||
</Box> )}
|
||||
</form>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleAdd} disabled={!newProvider.name || !newProvider.title}>
|
||||
Add
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>);
|
||||
}
|
||||
|
||||
export default AddProviderDialog;
|
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import { Grid, Typography, TextField } from '@mui/material';
|
||||
|
||||
function ClientSettingsTab({ provider, handleInputChange }) {
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`client_id`}
|
||||
label="Client ID"
|
||||
value={provider.clientconfig?.client_id || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`redirect_uri`}
|
||||
label="Redirect URI"
|
||||
value={provider.clientconfig?.redirect_uri || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`response_type`}
|
||||
label="Response Type"
|
||||
value={provider.clientconfig?.response_type || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`scope`}
|
||||
label="Scope"
|
||||
value={provider.clientconfig?.scope || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default ClientSettingsTab;
|
@ -0,0 +1,71 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Grid, TextField, FormControl, InputLabel, Select, MenuItem, Typography, Box } from '@mui/material';
|
||||
|
||||
function GeneralSettingsTab({ provider, handleInputChange, handleProviderChange }) {
|
||||
const [name, setName] = useState(provider.name || '');
|
||||
const [title, setTitle] = useState(provider.title || '');
|
||||
const [authenticationUserPropertyName, setAuthenticationUserPropertyName] = useState(provider.authenticationUserPropertyName || 'email');
|
||||
const [authenticationClaimName, setAuthenticationClaimName] = useState(provider.authenticationClaimName || 'email');
|
||||
const [imagePreview, setImagePreview] = useState(provider.icon || null);
|
||||
|
||||
useEffect(() => {
|
||||
const updatedProvider = {
|
||||
...provider,
|
||||
name,
|
||||
title,
|
||||
authenticationUserPropertyName,
|
||||
authenticationClaimName,
|
||||
icon: imagePreview
|
||||
};
|
||||
handleProviderChange(updatedProvider);
|
||||
}, [name, title, authenticationUserPropertyName, authenticationClaimName, imagePreview]);
|
||||
|
||||
const handleImageChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setImagePreview(reader.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField fullWidth label="Name" value={name} onChange={(e) => setName(e.target.value)} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField fullWidth label="Title" value={title} onChange={(e) => setTitle(e.target.value)} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormControl fullWidth>
|
||||
<InputLabel>Authentication User Property Name</InputLabel>
|
||||
<Select value={authenticationUserPropertyName} onChange={(e) => setAuthenticationUserPropertyName(e.target.value)}>
|
||||
<MenuItem value="name">Name</MenuItem>
|
||||
<MenuItem value="OSUser">OSUser</MenuItem>
|
||||
<MenuItem value="email">Email</MenuItem>
|
||||
<MenuItem value="matchingKey">Matching Key</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField fullWidth label="Authentication Claim Name" value={authenticationClaimName} onChange={(e) => setAuthenticationClaimName(e.target.value)} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<input accept="image/*" style={{ display: 'none' }} id="icon-upload" type="file" onChange={handleImageChange} />
|
||||
<label htmlFor="icon-upload">
|
||||
<Typography>Upload Icon</Typography>
|
||||
</label>
|
||||
</Grid>
|
||||
{imagePreview && (
|
||||
<Grid item xs={12}>
|
||||
<Box>
|
||||
<img src={imagePreview} alt="Preview" style={{ width: '100px', height: 'auto' }} />
|
||||
</Box>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default GeneralSettingsTab;
|
@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { Grid, Typography, TextField } from '@mui/material';
|
||||
|
||||
function GeneralSettingsTab({ provider, handleInputChange }) {
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`issuer`}
|
||||
label="Issuer"
|
||||
value={provider.providerconfig?.issuer || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`authorization_endpoint`}
|
||||
label="Authorization Endpoint"
|
||||
value={provider.providerconfig?.authorization_endpoint || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`token_endpoint`}
|
||||
label="Token Endpoint"
|
||||
value={provider.providerconfig?.token_endpoint || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`jwks_uri`}
|
||||
label="JWKS URI"
|
||||
value={provider.providerconfig?.jwks_uri || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`userinfo_endpoint`}
|
||||
label="Userinfo Endpoint"
|
||||
value={provider.providerconfig?.userinfo_endpoint || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
fullWidth
|
||||
name={`scopes_supported`}
|
||||
label="Scopes Supported"
|
||||
value={provider.providerconfig?.scopes_supported?.join(', ') || ''}
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default GeneralSettingsTab;
|
17
react_frontend/src/index.js
Normal file
17
react_frontend/src/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import { ThemeProvider } from '@mui/material/styles';
|
||||
import App from './App';
|
||||
import theme from './darkTheme';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
const root = ReactDOM.createRoot(rootElement);
|
||||
|
||||
root.render(
|
||||
<ThemeProvider theme={theme}>
|
||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||
<CssBaseline />
|
||||
<App />
|
||||
</ThemeProvider>,
|
||||
);
|
19
react_frontend/src/lightTheme.js
Normal file
19
react_frontend/src/lightTheme.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { red } from '@mui/material/colors';
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
|
||||
// A custom theme for this app
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#556cd6',
|
||||
},
|
||||
secondary: {
|
||||
main: '#19857b',
|
||||
},
|
||||
error: {
|
||||
main: red.A400,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default theme;
|
69
react_frontend/src/serverManagement.js
Normal file
69
react_frontend/src/serverManagement.js
Normal file
@ -0,0 +1,69 @@
|
||||
// serverManagement.js
|
||||
|
||||
export const restartServer = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ws/restart`);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return { success: true, message: 'Сервер успешно перезапущен' };
|
||||
} else {
|
||||
return { success: false, message: `Ошибка при перезапуске сервера: ${response.statusText}` };
|
||||
}
|
||||
} catch (error) {
|
||||
return { success: false, message: `Ошибка при перезапуске сервера: ${error.message}` };
|
||||
}
|
||||
};
|
||||
|
||||
export const stopServer = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ws/stop`);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return { success: true, message: 'Сервер успешно остановлен' };
|
||||
} else {
|
||||
return { success: false, message: `Ошибка при остановке сервера: ${response.statusText}` };
|
||||
}
|
||||
} catch (error) {
|
||||
return { success: false, message: `Ошибка при остановке сервера: ${error.message}` };
|
||||
}
|
||||
};
|
||||
|
||||
export const startServer = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/v1/ws/start`);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return { success: true, message: 'Сервер успешно запущен' };
|
||||
} else {
|
||||
return { success: false, message: `Ошибка при запуске сервера: ${response.statusText}` };
|
||||
}
|
||||
} catch (error) {
|
||||
return { success: false, message: `Ошибка при запуске сервера: ${error.message}` };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const saveToServer = async (jsonData) => {
|
||||
try {
|
||||
const response = await fetch('/api/v1/updateconfig', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(jsonData, null, 2)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
return { success: true, message: 'Уонфигурация успешно записана' };
|
||||
} else {
|
||||
return { success: false, message: `Ошибка при запуске сервера: ${response.statusText}` };
|
||||
}
|
||||
} catch (error) {
|
||||
return { success: false, message: `Ошибка при запуске сервера: ${error.message}` };
|
||||
}
|
||||
};
|
@ -4,7 +4,7 @@
|
||||
"Порт": 3333,
|
||||
"ЗадержкаПередЧтениемСокета": 65,
|
||||
"КаталогиСФайлами": {
|
||||
"/assets": "./view/assets"
|
||||
"/": "./view/"
|
||||
}
|
||||
},
|
||||
"Публикатор": {
|
||||
|
13
webserver/app/view/asset-manifest.json
Normal file
13
webserver/app/view/asset-manifest.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.b72ea8bc.css",
|
||||
"main.js": "/static/js/main.134517c4.js",
|
||||
"index.html": "/index.html",
|
||||
"main.b72ea8bc.css.map": "/static/css/main.b72ea8bc.css.map",
|
||||
"main.134517c4.js.map": "/static/js/main.134517c4.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.b72ea8bc.css",
|
||||
"static/js/main.134517c4.js"
|
||||
]
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
webserver/app/view/favicon.ico
Normal file
BIN
webserver/app/view/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
@ -1,206 +1 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
|
||||
<title>1С Публикатор</title>
|
||||
<link rel="icon" type="image/x-icon" href="/assets/dist/img/favicon.ico">
|
||||
<link href="/assets/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.0/css/all.min.css">
|
||||
|
||||
<style>
|
||||
html,
|
||||
body{
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.b-example-divider {
|
||||
height: 3rem;
|
||||
background-color: rgba(0, 0, 0, .1);
|
||||
border: solid rgba(0, 0, 0, .15);
|
||||
border-width: 1px 0;
|
||||
box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.b-example-vr {
|
||||
flex-shrink: 0;
|
||||
width: 1.5rem;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.bi {
|
||||
vertical-align: -.125em;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.nav-scroller {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 2.75rem;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.nav-scroller .nav {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
padding-bottom: 1rem;
|
||||
margin-top: -1px;
|
||||
overflow-x: auto;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
#logo {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#editor {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark" aria-label="Fifth navbar example">
|
||||
<div class="container-fluid">
|
||||
<img id="logo" src="/assets/dist/img/logo.png"
|
||||
alt="1C Logo">
|
||||
<a class="navbar-brand">Публикатор 1с</a>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarsExample05">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="reload"><i class="fas fa-redo"></i> Обновить</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="submit"><i class="fas fa-save"></i> Сохранить конфигурацию</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown"
|
||||
aria-expanded="false">Управление web-сервером</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" id="restart"><i class="fas fa-sync-alt"></i>
|
||||
Перезапустить</a></li>
|
||||
<li><a class="dropdown-item" id="start"><i class="fas fa-play"></i> Запустить</a></li>
|
||||
<li><a class="dropdown-item" id="stop"> <i class="fas fa-stop"></i> Остановить</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</main>
|
||||
|
||||
<div id="editor"></div>
|
||||
|
||||
<script src="/assets/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://unpkg.com/monaco-editor@0.28.0/min/vs/loader.js"></script>
|
||||
<script>
|
||||
require.config({
|
||||
paths: {
|
||||
'vs': 'https://unpkg.com/monaco-editor@0.28.0/min/vs'
|
||||
}
|
||||
});
|
||||
|
||||
require(['vs/editor/editor.main'], function () {
|
||||
// Initialize the Monaco Editor
|
||||
const editor = monaco.editor.create(document.getElementById('editor'), {
|
||||
language: 'json',
|
||||
theme: 'vs-dark'
|
||||
});
|
||||
|
||||
// Load JSON from the API
|
||||
async function loadJSON() {
|
||||
const response = await fetch('./api/v1/getconfig.json');
|
||||
const data = await response.text();
|
||||
editor.setValue(data);
|
||||
}
|
||||
|
||||
// Post JSON to the API
|
||||
async function postJSON(json) {
|
||||
const response = await fetch('./api/v1/updateconfig', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: json
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Add click event listener to the submit button
|
||||
document.getElementById('submit').addEventListener('click', async () => {
|
||||
if (confirm('Вы уверены, что хотите сохранить конфигурацию?')) {
|
||||
const json = editor.getValue();
|
||||
try {
|
||||
const response = await postJSON(json);
|
||||
if (response.ok) {
|
||||
if (confirm('Конфигурация успешно сохранена! \n Перезапустить веб-сервер?')) {
|
||||
await fetch('./api/v1/ws/restart');
|
||||
}
|
||||
} else {
|
||||
alert('Ошибка при сохранении конфигурации!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
alert('Ошибка при сохранении конфигурации!');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add click event listener to the reload button
|
||||
document.getElementById('reload').addEventListener('click', async () => {
|
||||
if (confirm('Вы уверены, что хотите обновить конфигурацию? Это заменит текущую конфигурацию в редакторе.')) {
|
||||
await loadJSON();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Add click event listeners to the web server control buttons
|
||||
document.getElementById('restart').addEventListener('click', async () => {
|
||||
await fetch('./api/v1/ws/restart');
|
||||
});
|
||||
|
||||
document.getElementById('start').addEventListener('click', async () => {
|
||||
await fetch('./api/v1/ws/start');
|
||||
});
|
||||
|
||||
document.getElementById('stop').addEventListener('click', async () => {
|
||||
await fetch('./api/v1/ws/stop');
|
||||
});
|
||||
|
||||
// Load JSON on startup
|
||||
loadJSON();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="shortcut icon" href="/favicon.ico"/><meta name="viewport" content="initial-scale=1,width=device-width"/><meta name="theme-color" content="#000000"/><link rel="manifest" href="/manifest.json"/><title>Публикатор 1с</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"/><link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"/><script defer="defer" src="/static/js/main.134517c4.js"></script><link href="/static/css/main.b72ea8bc.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
BIN
webserver/app/view/logo.png
Normal file
BIN
webserver/app/view/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
15
webserver/app/view/manifest.json
Normal file
15
webserver/app/view/manifest.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "Your Orders",
|
||||
"name": "Your Orders",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
2
webserver/app/view/static/css/main.b72ea8bc.css
Normal file
2
webserver/app/view/static/css/main.b72ea8bc.css
Normal file
@ -0,0 +1,2 @@
|
||||
.Publications_publicationsWrapper__JNEwF{display:flex;flex-direction:column;min-height:100vh}.Publications_publicationsContainer__WBZVM{flex-grow:1;margin-bottom:40px}.Publications_addPublicationButton__PNBAc{align-self:flex-end;margin-bottom:-80px}.Publications_publicationCard__X58tU{height:200px;width:100%}.EditPublicationForm_grid__ds-Pi{align-items:center;display:flex;justify-content:center}.Content_container__pHxpH{align-items:left;display:flex;height:100%;padding-top:64px}.Content_innerContainer__-G-LS{flex:1 1;flex-direction:column;overflow:auto}.Content_publicationsContainer__2CUys{flex:1 1;overflow:auto}.Content_exportJsonContainer__5vo0J{display:flex;flex:1 1;overflow:auto}.Content_exportJsonButton__R7pbR{margin-top:auto}
|
||||
/*# sourceMappingURL=main.b72ea8bc.css.map*/
|
1
webserver/app/view/static/css/main.b72ea8bc.css.map
Normal file
1
webserver/app/view/static/css/main.b72ea8bc.css.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/css/main.b72ea8bc.css","mappings":"AAAA,yCACE,YAAa,CACb,qBAAsB,CACtB,gBACF,CAEA,2CACE,WAAY,CACZ,kBACF,CAEA,0CAEE,mBAAoB,CADpB,mBAEF,CAEA,qCACE,YAAa,CACb,UACF,CCnBA,iCAGE,kBAAmB,CAFnB,YAAa,CACb,sBAEF,CCFA,0BAIE,gBAAiB,CAHjB,YAAa,CACb,WAAY,CACZ,gBAEF,CAGA,+BACE,QAAO,CAEP,qBAAsB,CADtB,aAEF,CACA,sCACE,QAAO,CACP,aACF,CAEA,oCAEE,YAAa,CADb,QAAO,CAEP,aACF,CAEA,iCACE,eACF","sources":["css/Publications.module.css","ediPublicationFormTabs/EditPublicationForm.module.css","css/Content.module.css"],"sourcesContent":[".publicationsWrapper {\r\n display: flex;\r\n flex-direction: column;\r\n min-height: 100vh;\r\n}\r\n\r\n.publicationsContainer {\r\n flex-grow: 1;\r\n margin-bottom: 40px; /* или другое значение в зависимости от размера кнопки */\r\n}\r\n\r\n.addPublicationButton {\r\n margin-bottom: -80px;\r\n align-self: flex-end;\r\n}\r\n\r\n.publicationCard {\r\n height: 200px;\r\n width: 100%;\r\n}",".grid {\r\n display: flex;\r\n justify-content: center;\r\n align-items: center;\r\n}","\r\n\r\n.container {\r\n display: flex;\r\n height: 100%;\r\n padding-top: 64px;\r\n align-items: left;\r\n}\r\n\r\n\r\n.innerContainer {\r\n flex: 1;\r\n overflow: auto;\r\n flex-direction: column;\r\n}\r\n.publicationsContainer {\r\n flex: 1;\r\n overflow: auto;\r\n}\r\n\r\n.exportJsonContainer {\r\n flex: 1;\r\n display: flex;\r\n overflow: auto;\r\n}\r\n\r\n.exportJsonButton {\r\n margin-top: auto;\r\n}"],"names":[],"sourceRoot":""}
|
3
webserver/app/view/static/js/main.134517c4.js
Normal file
3
webserver/app/view/static/js/main.134517c4.js
Normal file
File diff suppressed because one or more lines are too long
68
webserver/app/view/static/js/main.134517c4.js.LICENSE.txt
Normal file
68
webserver/app/view/static/js/main.134517c4.js.LICENSE.txt
Normal file
@ -0,0 +1,68 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @mui/styled-engine v5.11.11
|
||||
*
|
||||
* @license MIT
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
1
webserver/app/view/static/js/main.134517c4.js.map
Normal file
1
webserver/app/view/static/js/main.134517c4.js.map
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user