IT Service Desk — это полнофункциональное веб-приложение для управления инцидентами и оборудованием в IT-инфраструктуре организации. Система предоставляет комплексное решение для отслеживания инцидентов, управления оборудованием, генерации отчетов и аналитики.
- Система ролей и авторизации: Три уровня доступа (Администратор, Техник, Пользователь)
- Управление инцидентами: Создание, отслеживание и разрешение IT-инцидентов
- Учет оборудования: Полный контроль над IT-активами организации
- Аналитическая панель: Визуализация ключевых метрик и показателей
- Система уведомлений: Мгновенные уведомления о важных событиях
- Генерация отчетов: Создание отчетов в различных форматах (PDF, Excel, CSV)
- Безопасная аутентификация: JWT-токены для защиты API
- React.js
- Redux Toolkit для управления состоянием
- React Router для навигации
- Axios для HTTP-запросов
- Socket.io-client для веб-сокетов
- Chart.js для визуализации данных
- Formik и Yup для валидации форм
- Styled-components для стилизации
- Node.js с Express
- MongoDB с Mongoose ORM
- JWT для аутентификации
- Socket.io для реального времени
- Winston для логирования
- Nodemailer для отправки email
- PDFKit и XLSX для генерации отчетов
- Jest для тестирования
- Node.js 14.x или выше
- MongoDB 4.x или выше
- npm 6.x или выше
git clone https://github.com/yourusername/it-service-desk.git
cd it-service-desk- Создайте файлы
.envв директорияхserverиclientна основе предоставленных примеров:
cp .env.example .env
cp server/.env.example server/.env
cp client/.env.example client/.env- Отредактируйте файлы
.envв соответствии с вашей конфигурацией
# Server Configuration
PORT=5000
NODE_ENV=development
# MongoDB Connection
MONGODB_URI=mongodb://localhost:27017/it-service-desk
# JWT Configuration
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRES_IN=1d
JWT_REFRESH_EXPIRES_IN=7d
# Email Configuration
SMTP_HOST=smtp.example.com
SMTP_PORT=587
[email protected]
SMTP_PASS=your_email_password
[email protected]
# WebSocket Configuration
WS_PORT=5001
REACT_APP_API_URL=http://localhost:5000/api
REACT_APP_WS_URL=http://localhost:5001
REACT_APP_VERSION=$npm_package_version
# Установка зависимостей для корневого проекта
npm install
# Установка зависимостей для серверной части
cd server
npm install
# Установка зависимостей для клиентской части
cd ../client
npm install# Запуск серверной части
cd server
npm run dev
# Запуск клиентской части (в отдельном терминале)
cd client
npm start# Сборка клиентской части
cd client
npm run build
# Запуск серверной части в производственном режиме
cd ../server
npm start# Сборка и запуск контейнеров
docker-compose up -d
# Остановка контейнеров
docker-compose downit-service-desk/
├── client/ # Клиентская часть (React)
│ ├── public/ # Статические файлы
│ └── src/ # Исходный код React
│ ├── assets/ # Изображения, шрифты и т.д.
│ ├── components/ # Общие компоненты
│ │ └── common/ # Переиспользуемые UI компоненты
│ ├── hooks/ # Пользовательские React хуки
│ ├── modules/ # Модули приложения
│ │ ├── auth/ # Модуль аутентификации
│ │ ├── dashboard/ # Модуль дашборда
│ │ ├── equipment/ # Модуль оборудования
│ │ ├── incidents/ # Модуль инцидентов
│ │ ├── notifications/ # Модуль уведомлений
│ │ └── reports/ # Модуль отчетов
│ ├── services/ # Сервисы
│ │ ├── api/ # API клиенты
│ │ └── websocket/ # WebSocket клиенты
│ ├── store/ # Redux хранилище
│ └── utils/ # Утилиты и хелперы
├── server/ # Серверная часть (Node.js)
│ ├── config/ # Конфигурация сервера
│ ├── controllers/ # Контроллеры API
│ ├── middleware/ # Промежуточные обработчики
│ ├── models/ # Mongoose модели
│ ├── routes/ # Маршруты API
│ ├── services/ # Бизнес-логика
│ ├── tests/ # Тесты
│ └── utils/ # Утилиты и хелперы
└── docker-compose.yml # Docker Compose конфигурация
POST /api/auth/register
Тело запроса:
{
"email": "[email protected]",
"password": "securePassword123",
"username": "johndoe",
"firstName": "John",
"lastName": "Doe",
"role": "USER",
"department": "IT",
"position": "Developer"
}Ответ:
{
"user": {
"id": "60d21b4667d0d8992e610c85",
"email": "[email protected]",
"username": "johndoe",
"firstName": "John",
"lastName": "Doe",
"role": "USER",
"department": "IT",
"position": "Developer",
"createdAt": "2023-06-22T10:00:00.000Z",
"updatedAt": "2023-06-22T10:00:00.000Z"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}POST /api/auth/login
Тело запроса:
{
"email": "[email protected]",
"password": "securePassword123"
}Ответ:
{
"user": {
"id": "60d21b4667d0d8992e610c85",
"email": "[email protected]",
"username": "johndoe",
"role": "USER",
"firstName": "John",
"lastName": "Doe"
},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}GET /api/auth/me
Заголовки:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Ответ:
{
"user": {
"id": "60d21b4667d0d8992e610c85",
"email": "[email protected]",
"username": "johndoe",
"role": "USER",
"firstName": "John",
"lastName": "Doe",
"department": "IT",
"position": "Developer"
}
}GET /api/incidents
Параметры запроса:
status=OPEN,IN_PROGRESS
priority=HIGH,MEDIUM
page=1
limit=10
sortBy=createdAt
sortOrder=desc
Ответ:
{
"incidents": [
{
"id": "60d21b4667d0d8992e610c85",
"title": "Network connectivity issue",
"description": "Unable to connect to the internet",
"status": "OPEN",
"priority": "HIGH",
"assignedTo": {
"id": "60d21b4667d0d8992e610c86",
"username": "techsupport"
},
"createdBy": {
"id": "60d21b4667d0d8992e610c87",
"username": "johndoe"
},
"createdAt": "2023-06-22T10:00:00.000Z",
"updatedAt": "2023-06-22T10:00:00.000Z"
}
],
"pagination": {
"total": 45,
"page": 1,
"limit": 10,
"pages": 5
}
}POST /api/incidents
Тело запроса:
{
"title": "Software installation request",
"description": "Need to install Adobe Photoshop on my workstation",
"priority": "MEDIUM",
"equipmentId": "60d21b4667d0d8992e610c88",
"attachments": []
}Ответ:
{
"id": "60d21b4667d0d8992e610c89",
"title": "Software installation request",
"description": "Need to install Adobe Photoshop on my workstation",
"status": "OPEN",
"priority": "MEDIUM",
"equipmentId": "60d21b4667d0d8992e610c88",
"createdBy": {
"id": "60d21b4667d0d8992e610c87",
"username": "johndoe"
},
"createdAt": "2023-06-22T10:00:00.000Z",
"updatedAt": "2023-06-22T10:00:00.000Z"
}GET /api/equipment
Параметры запроса:
type=LAPTOP,DESKTOP
status=ACTIVE,MAINTENANCE
department=IT,MARKETING
page=1
limit=10
Ответ:
{
"equipment": [
{
"id": "60d21b4667d0d8992e610c90",
"name": "Dell XPS 15",
"type": "LAPTOP",
"serialNumber": "DXPS15-2023-001",
"status": "ACTIVE",
"purchaseDate": "2022-01-15T00:00:00.000Z",
"assignedTo": {
"id": "60d21b4667d0d8992e610c87",
"username": "johndoe"
},
"department": "IT",
"location": "HQ - Floor 2",
"specifications": {
"cpu": "Intel i7-11800H",
"ram": "32GB",
"storage": "1TB SSD",
"os": "Windows 11 Pro"
},
"createdAt": "2023-06-22T10:00:00.000Z",
"updatedAt": "2023-06-22T10:00:00.000Z"
}
],
"pagination": {
"total": 120,
"page": 1,
"limit": 10,
"pages": 12
}
}POST /api/reports/generate
Тело запроса:
{
"type": "incidents",
"format": "pdf",
"title": "Monthly Incident Report",
"filters": {
"startDate": "2023-06-01",
"endDate": "2023-06-30",
"status": ["OPEN", "IN_PROGRESS", "RESOLVED"],
"priority": ["HIGH", "MEDIUM"]
},
"includeCharts": true,
"groupBy": "status"
}Ответ:
{
"reportUrl": "/api/reports/download/report-60d21b4667d0d8992e610c91.pdf",
"reportOptions": {
"type": "incidents",
"format": "pdf",
"title": "Monthly Incident Report",
"filters": {
"startDate": "2023-06-01",
"endDate": "2023-06-30",
"status": ["OPEN", "IN_PROGRESS", "RESOLVED"],
"priority": ["HIGH", "MEDIUM"]
},
"includeCharts": true,
"groupBy": "status"
}
}GET /api/dashboard/statistics
Ответ:
{
"incidents": {
"total": 145,
"open": 35,
"inProgress": 42,
"resolved": 68,
"byPriority": {
"HIGH": 28,
"MEDIUM": 67,
"LOW": 50
}
},
"equipment": {
"total": 230,
"active": 198,
"maintenance": 22,
"retired": 10,
"byType": {
"LAPTOP": 120,
"DESKTOP": 80,
"PRINTER": 15,
"SERVER": 10,
"NETWORK": 5
}
},
"performance": {
"averageResolutionTime": 18.5,
"resolutionTimeByPriority": {
"HIGH": 8.2,
"MEDIUM": 24.7,
"LOW": 36.3
}
}
}import { useDispatch } from 'react-redux';
import { AuthModule } from './modules/auth';
import { useState } from 'react';
const LoginPage = () => {
const dispatch = useDispatch();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleLogin = async (e) => {
e.preventDefault();
try {
await dispatch(AuthModule.login(email, password));
// Перенаправление на дашборд после успешной авторизации
window.location.href = '/dashboard';
} catch (err) {
setError('Неверные учетные данные');
}
};
return (
<form onSubmit={handleLogin}>
<div>
<label>Email:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
{error && <div className="error">{error}</div>}
<button type="submit">Login</button>
</form>
);
};import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { createIncident } from './modules/incidents/incidentThunks';
const CreateIncidentForm = () => {
const dispatch = useDispatch();
const [formData, setFormData] = useState({
title: '',
description: '',
priority: 'MEDIUM',
equipmentId: '',
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
await dispatch(createIncident(formData));
alert('Инцидент успешно создан!');
// Сброс формы
setFormData({
title: '',
description: '',
priority: 'MEDIUM',
equipmentId: '',
});
} catch (error) {
alert('Ошибка при создании инцидента');
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Заголовок:</label>
<input
type="text"
name="title"
value={formData.title}
onChange={handleChange}
required
/>
</div>
<div>
<label>Описание:</label>
<textarea
name="description"
value={formData.description}
onChange={handleChange}
required
/>
</div>
<div>
<label>Приоритет:</label>
<select
name="priority"
value={formData.priority}
onChange={handleChange}
>
<option value="LOW">Низкий</option>
<option value="MEDIUM">Средний</option>
<option value="HIGH">Высокий</option>
</select>
</div>
<div>
<label>ID оборудования (опционально):</label>
<input
type="text"
name="equipmentId"
value={formData.equipmentId}
onChange={handleChange}
/>
</div>
<button type="submit">Создать инцидент</button>
</form>
);
};import { useState } from 'react';
import { useDispatch } from 'react-redux';
import { ReportModule } from './modules/reports';
import { ReportType, ReportFormat } from './modules/reports/reportTypes';
const GenerateReportForm = () => {
const dispatch = useDispatch();
const [reportOptions, setReportOptions] = useState({
type: ReportType.INCIDENTS,
format: ReportFormat.PDF,
title: 'Отчет по инцидентам',
filters: {
startDate: '',
endDate: '',
status: [],
priority: []
},
includeCharts: true
});
const handleChange = (e) => {
const { name, value } = e.target;
setReportOptions(prev => ({
...prev,
[name]: value
}));
};
const handleFilterChange = (e) => {
const { name, value } = e.target;
setReportOptions(prev => ({
...prev,
filters: {
...prev.filters,
[name]: value
}
}));
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
await dispatch(ReportModule.generateReport(reportOptions));
alert('Отчет успешно сгенерирован!');
} catch (error) {
alert('Ошибка при генерации отчета');
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>Тип отчета:</label>
<select
name="type"
value={reportOptions.type}
onChange={handleChange}
>
<option value={ReportType.INCIDENTS}>Инциденты</option>
<option value={ReportType.EQUIPMENT}>Оборудование</option>
<option value={ReportType.USERS}>Пользователи</option>
<option value={ReportType.PERFORMANCE}>Производительность</option>
</select>
</div>
<div>
<label>Формат:</label>
<select
name="format"
value={reportOptions.format}
onChange={handleChange}
>
<option value={ReportFormat.PDF}>PDF</option>
<option value={ReportFormat.EXCEL}>Excel</option>
<option value={ReportFormat.CSV}>CSV</option>
</select>
</div>
<div>
<label>Заголовок отчета:</label>
<input
type="text"
name="title"
value={reportOptions.title}
onChange={handleChange}
required
/>
</div>
<div>
<label>Дата начала:</label>
<input
type="date"
name="startDate"
value={reportOptions.filters.startDate}
onChange={handleFilterChange}
/>
</div>
<div>
<label>Дата окончания:</label>
<input
type="date"
name="endDate"
value={reportOptions.filters.endDate}
onChange={handleFilterChange}
/>
</div>
<div>
<label>Включить диаграммы:</label>
<input
type="checkbox"
name="includeCharts"
checked={reportOptions.includeCharts}
onChange={(e) => setReportOptions(prev => ({
...prev,
includeCharts: e.target.checked
}))}
/>
</div>
<button type="submit">Сгенерировать отчет</button>
</form>
);
};- Создайте контроллер в директории
server/controllers - Добавьте маршрут в соответствующий файл в директории
server/routes - Зарегистрируйте маршрут в
server/routes/index.js
Пример контроллера:
// server/controllers/example.controller.js
const Example = require('../models/example.model');
exports.getAll = async (req, res, next) => {
try {
const examples = await Example.find();
res.status(200).json({ examples });
} catch (error) {
next(error);
}
};
exports.create = async (req, res, next) => {
try {
const example = new Example(req.body);
await example.save();
res.status(201).json(example);
} catch (error) {
next(error);
}
};Пример маршрута:
// server/routes/example.routes.js
const express = require('express');
const router = express.Router();
const exampleController = require('../controllers/example.controller');
const { authMiddleware } = require('../middleware');
router.get('/', authMiddleware, exampleController.getAll);
router.post('/', authMiddleware, exampleController.create);
module.exports = router;Регистрация маршрута:
// server/routes/index.js
const express = require('express');
const router = express.Router();
const authRoutes = require('./auth.routes');
const incidentRoutes = require('./incident.routes');
const equipmentRoutes = require('./equipment.routes');
const exampleRoutes = require('./example.routes'); // Добавлено
router.use('/auth', authRoutes);
router.use('/incidents', incidentRoutes);
router.use('/equipment', equipmentRoutes);
router.use('/examples', exampleRoutes); // Добавлено
module.exports = router;- Создайте структуру директорий в
client/src/modules/[module_name] - Создайте типы, slice, thunks и компоненты
- Добавьте reducer в корневой reducer
Пример структуры модуля:
client/src/modules/example/
├── components/
│ ├── ExampleForm.jsx
│ └── ExampleList.jsx
├── pages/
│ └── ExamplePage.jsx
├── store/
│ ├── exampleSlice.js
│ └── exampleThunks.js
├── exampleTypes.js
└── index.js
Пример slice:
// client/src/modules/example/store/exampleSlice.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
examples: [],
loading: false,
error: null
};
const exampleSlice = createSlice({
name: 'examples',
initialState,
reducers: {
fetchExamplesStart: (state) => {
state.loading = true;
state.error = null;
},
fetchExamplesSuccess: (state, action) => {
state.examples = action.payload;
state.loading = false;
},
fetchExamplesFailure: (state, action) => {
state.loading = false;
state.error = action.payload;
}
}
});
export const {
fetchExamplesStart,
fetchExamplesSuccess,
fetchExamplesFailure
} = exampleSlice.actions;
export default exampleSlice.reducer;Добавление в корневой reducer:
// client/src/store/rootReducer.js
import { combineReducers } from '@reduxjs/toolkit';
import authReducer from '../modules/auth/store/authSlice';
import incidentsReducer from '../modules/incidents/store/incidentsSlice';
import equipmentReducer from '../modules/equipment/store/equipmentSlice';
import exampleReducer from '../modules/example/store/exampleSlice'; // Добавлено
const rootReducer = combineReducers({
auth: authReducer,
incidents: incidentsReducer,
equipment: equipmentReducer,
examples: exampleReducer // Добавлено
});
export default rootReducer;# Запуск тестов серверной части
cd server
npm test
# Запуск тестов с покрытием
npm run test:coverage
# Запуск тестов клиентской части
cd client
npm test// server/tests/auth.test.js
const request = require('supertest');
const app = require('../app');
const User = require('../models/user.model');
const mongoose = require('mongoose');
describe('Auth API', () => {
beforeAll(async () => {
// Подключение к тестовой базе данных
await mongoose.connect(process.env.MONGODB_URI_TEST);
});
afterAll(async () => {
// Отключение от базы данных
await mongoose.connection.close();
});
beforeEach(async () => {
// Очистка коллекции пользователей перед каждым тестом
await User.deleteMany({});
});
it('should register a new user', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({
email: '[email protected]',
password: 'password123',
username: 'testuser'
});
expect(res.statusCode).toEqual(201);
expect(res.body).toHaveProperty('token');
expect(res.body.user).toHaveProperty('email', '[email protected]');
});
it('should login an existing user', async () => {
// Создание пользователя
await request(app)
.post('/api/auth/register')
.send({
email: '[email protected]',
password: 'password123',
username: 'testuser'
});
// Логин
const res = await request(app)
.post('/api/auth/login')
.send({
email: '[email protected]',
password: 'password123'
});
expect(res.statusCode).toEqual(200);
expect(res.body).toHaveProperty('token');
expect(res.body.user).toHaveProperty('email', '[email protected]');
});
});- Соберите клиентскую часть:
cd client
npm run build- Настройте переменные окружения для производства:
# server/.env
NODE_ENV=production
PORT=5000
MONGODB_URI=mongodb://your-production-mongodb-uri
JWT_SECRET=your-production-jwt-secret- Запустите сервер:
cd server
npm start- Соберите и запустите контейнеры:
docker-compose -f docker-compose.prod.yml up -d- Форкните репозиторий
- Создайте ветку для вашей функции (
git checkout -b feature/amazing-feature) - Зафиксируйте ваши изменения (
git commit -m 'Add some amazing feature') - Отправьте ветку (
git push origin feature/amazing-feature) - Откройте Pull Request
Этот проект лицензирован под MIT License - см. файл LICENSE для деталей.
Имя Автора - ваш[email protected]
Ссылка на проект: https://github.com/yourusername/it-service-desk