Commit b73921eb authored by avelichko's avatar avelichko

Правки

parent 46f27ebd
{
"git.ignoreLimitWarning": true
}
\ No newline at end of file
This diff is collapsed.
from datetime import datetime
from typing import Any, Dict, List, Optional, Union
from enum import Enum
class Function:
def __init__(self, id: str, name: str, language: str, platform: str) -> None:
self.id: str = id
self.name: str = name
self.language: str = language
self.platform: str = platform
class Version:
def __init__(
self, function: Function, version_number: str, origin: str, code: str, created_at: datetime
) -> None:
self.function: Function = function
self.version_number: str = version_number
self.origin: str = origin
self.code: str = code
self.created_at: datetime = created_at
class OptimizationReport:
def __init__(
self,
version: Version,
report_id: str,
recommendations: List[str],
optimized_code: str,
metrics_before: Dict[str, Any],
metrics_after: Dict[str, Any],
created_at: datetime,
) -> None:
self.version: Version = version
self.report_id: str = report_id
self.recommendations: List[str] = recommendations
self.optimized_code: str = optimized_code
self.metrics_before: Dict[str, Any] = metrics_before
self.metrics_after: Dict[str, Any] = metrics_after
self.created_at: datetime = created_at
class TestConfiguration:
def __init__(
self, version: Version, runtime: str, iterations: int, parallelism: int, timeout: int
) -> None:
self.version: Version = version
self.runtime: str = runtime
self.iterations: int = iterations
self.parallelism: int = parallelism
self.timeout: int = timeout
class TestRunStatus(Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
class TestRun:
def __init__(
self,
test_configuration: TestConfiguration,
run_id: str,
started_at: datetime,
ended_at: Optional[datetime] = None,
status: TestRunStatus = TestRunStatus.PENDING,
) -> None:
self.test_configuration: TestConfiguration = test_configuration
self.run_id: str = run_id
self.started_at: datetime = started_at
self.ended_at: Optional[datetime] = ended_at
self.status: TestRunStatus = status
class Metric:
def __init__(
self, test_run: TestRun, type: str, percentiles: Dict[str, float], timestamp: datetime
) -> None:
self.test_run: TestRun = test_run
self.type: str = type
self.percentiles: Dict[str, float] = percentiles
self.timestamp: datetime = timestamp
class TestProfile:
def __init__(
self,
test_run: TestRun,
profile_id: str,
function_name: str,
version_number: str,
run_timestamp: datetime,
) -> None:
self.test_run: TestRun = test_run
self.profile_id: str = profile_id
self.function_name: str = function_name
self.version_number: str = version_number
self.run_timestamp: datetime = run_timestamp
class Draft:
def __init__(self, version: Version, content: str, last_updated: datetime) -> None:
self.version: Version = version
self.content: str = content
self.last_updated: datetime = last_updated
\ No newline at end of file
from typing import Any
from dataclasses import dataclass
@dataclass
class Result:
data: Any
@dataclass
class Metrics:
time: int
resources: dict[str, int]
@dataclass
class ComparisonReport:
improvements: dict[str, float]
class TestScenario:
def __init__(self, id: str, name: str, configuration: dict[str, Any]) -> None:
self.id = id
self.name = name
self.configuration = configuration
def execute(self) -> Result:
pass
class Optimizer:
def optimize(self, scenario: TestScenario) -> TestScenario:
pass
class MetricsCollector:
def collect_metrics(self, result: Result) -> Metrics:
pass
class ComparisonTool:
def compare(self, before: Metrics, after: Metrics) -> ComparisonReport:
pass
class RecommendationGenerator:
def generate_recommendations(self, report: ComparisonReport) -> list[str]:
pass
class WebPortal:
def show_results(self, report: ComparisonReport) -> None:
pass
def show_recommendations(self, recommendations: list[str]) -> None:
pass
\ No newline at end of file
diagrams/ClassDiagram.png

71.3 KB | W: | H:

diagrams/ClassDiagram.png

45 KB | W: | H:

diagrams/ClassDiagram.png
diagrams/ClassDiagram.png
diagrams/ClassDiagram.png
diagrams/ClassDiagram.png
  • 2-up
  • Swipe
  • Onion skin
diagrams/UseCase.png

29.4 KB | W: | H:

diagrams/UseCase.png

71.9 KB | W: | H:

diagrams/UseCase.png
diagrams/UseCase.png
diagrams/UseCase.png
diagrams/UseCase.png
  • 2-up
  • Swipe
  • Onion skin
@startuml ActivityDiagram @startuml Activity_Editor
title Диаграмма активности процесса тестирования Serverless title Диаграмма деятельности: Редактор кода
start partition Пользователь {
:Настроить тестовый сценарий; start
:Оптимизировать сценарий; :Открыть страницу "Редактор";
:Запустить оптимизированный тест; if (Режим = Create?) then (да)
:Собрать метрики оптимизированного теста; :Заполнить "Название";
:Запустить оригинальный тест; :Выбрать "Язык" и "Платформу";
:Собрать метрики оригинального теста; :Ввести код функции;
:Сравнить метрики; if (Код ≠ пустой) then (да)
:Сгенерировать рекомендации; :Нажать "Сохранить";
:Отобразить результаты; else
:Показать ошибку "Код не может быть пустым";
stop
endif
else (нет)
:Просмотреть существующий код;
endif
:Нажать кнопку "Редактировать" или "Оптимизировать";
}
partition Система {
if (Нажали "Оптимизировать") then (да)
:Отправить код в LLM;
:Получить оптимизированный код и рекомендации;
:Перейти в режим ViewOptimized;
elseif (Нажали "Редактировать")
:Перейти в режим Edit;
endif
if (В режиме Edit и есть изменения?) then (да)
:Сохранить новую версию;
:Перейти в режим View;
endif
}
stop
@enduml
@startuml Activity_Testing
title Диаграмма деятельности: Тестирование
partition Пользователь {
start
:Открыть страницу "Тестирование";
:Выбрать функцию и версию;
:Задать параметры теста (рантайм, итерации, параллельность, таймаут);
if (Все поля заполнены?) then (да)
:Нажать "Запустить тестирование";
else
:Показать тултип "Заполните все поля";
stop
endif
}
partition Система {
:Отправить запрос на запуск тестов;
:Отобразить прогресс-бар и секундомер;
while (Тестирование в процессе)
:Собирать метрики;
endwhile
}
partition Пользователь {
if (Нажали "Отмена" во время теста?) then (да)
:Подтвердить отмену;
stop
endif
}
partition Система {
:Тестирование завершено;
}
partition Пользователь {
:Показать экран "Результаты тестирования";
if (Нажали "Просмотр результатов") then (да)
:Перейти на вкладку "Результаты";
elseif (Нажали "Новый тест")
:Вернуться к конфигурации;
endif
}
stop
@enduml
@startuml Activity_Results
title Диаграмма деятельности: Результаты
partition Пользователь {
start
:Открыть страницу "Результаты";
:Показать два селектора профилей;
:Ожидать выбор в Селекторе 1;
if (Профиль 1 выбран?) then (да)
:Разблокировать Селектор 2;
else
stop
endif
:Ожидать выбор в Селекторе 2?;
}
partition Система {
if (Профиль 1 выбран) then
:Загрузить и отобразить данные профиля 1;
:Показать графики и "Детали теста 1";
endif
if (Профиль 2 выбран) then
:Загрузить и отобразить данные профиля 2;
:Показать графики сравнения;
:Показать "Детали теста 2" и "Сводку изменений";
endif
}
partition Пользователь {
:Сбросить селекторы или закрыть страницу;
}
stop stop
@enduml @enduml
\ No newline at end of file
@startuml ClassDiagram @startuml ClassDiagram
title Диаграмма классов системы тестирования Serverless title Диаграмма классов: Система тестирования Serverless
class СценарийТеста { class Function {
+id: String +id
+имя: String +name
+конфигурация: Map<String, Object> +language
+выполнить(): Результат +platform
} }
class Оптимизатор {
+оптимизировать(сценарий: СценарийТеста): СценарийТеста class Version {
} +versionNumber
class СборщикМетрик { +origin
+собратьМетрики(результат: Результат): Метрики +code
+createdAt
} }
class ИнструментСравнения {
+сравнить(до: Метрики, после: Метрики): ОтчетСравнения class OptimizationReport {
+reportId
+recommendations
+optimizedCode
+metricsBefore
+metricsAfter
+createdAt
} }
class ГенераторРекомендаций {
+сгенерироватьРекомендации(отчет: ОтчетСравнения): List<String> class TestConfiguration {
+runtime
+iterations
+parallelism
+timeout
} }
class ВебПортал {
+показатьРезультаты(отчет: ОтчетСравнения) class TestRun {
+показатьРекомендации(рекомендации: List<String>) +runId
+startedAt
+endedAt
+status
} }
class Результат {
+данные: Object class Metric {
+type
+percentiles
+timestamp
} }
class Метрики {
+время: Integer class TestProfile {
+ресурсы: Map<String, Integer> +profileId
+functionName
+versionNumber
+runTimestamp
} }
class ОтчетСравнения {
+улучшения: Map<String, Double> class Draft {
+content
+lastUpdated
} }
СценарийТеста --> Результат : "1" Function "1" -- "0..*" Version
Оптимизатор --> СценарийТеста : оптимизирует Version "1" -- "0..1" OptimizationReport
СборщикМетрик --> Метрики : собирает Version "1" -- "0..*" TestConfiguration
ИнструментСравнения --> ОтчетСравнения : создает отчет TestConfiguration "1" -- "1..*" TestRun
ГенераторРекомендаций --> "List<String>" : рекомендации TestRun "1" -- "0..*" Metric
ВебПортал --> ОтчетСравнения : отображает отчет TestRun "1" -- "0..1" TestProfile
ВебПортал --> "List<String>" : отображает рекомендации Version "1" -- "0..1" Draft
@enduml @enduml
\ No newline at end of file
@startuml SequenceDiagram @startuml Sequence_Optimization
title Диаграмма последовательности выполнения тестов Serverless title Диаграмма последовательности: Оптимизация кода
actor ОблачныйИнженер actor "Разработчик" as Dev
participant ВебПортал participant "Веб-интерфейс" as UI
participant ДвижокТестирования as TestEngine participant "Backend-сервис" as Backend
participant Оптимизатор participant "LLM-модуль" as LLM
participant СборщикМетрик
participant ИнструментСравнения
participant ГенераторРекомендаций
ОблачныйИнженер -> ВебПортал: настроитьТест(конфигСценария) Dev -> UI : Нажатие кнопки "Оптимизировать"
ВебПортал -> ДвижокТестирования: запуститьТест(конфигСценария) UI -> Backend : Отправить код функции
ДвижокТестирования -> Оптимизатор: оптимизировать(сценарий) Backend -> LLM : Запрос на оптимизацию
Оптимизатор --> ДвижокТестирования: оптимизированныйСценарий LLM --> Backend : Переработанный код + рекомендации
ДвижокТестирования -> СборщикМетрик: собратьМетрики(оптимизированныйСценарий) Backend --> UI : Отобразить новую версию и блок "Оптимизации"
СборщикМетрик --> ДвижокТестирования: метрикиОптимизированного @enduml
ДвижокТестирования --> ВебПортал: результатыОптимизированного
ВебПортал -> ДвижокТестирования: запуститьТест(оригСценарий) @startuml Sequence_Testing
ДвижокТестирования -> СборщикМетрик: собратьМетрики(оригСценарий) title Диаграмма последовательности: Запуск тестирования
СборщикМетрик --> ДвижокТестирования: метрикиОригинала actor "Инженер по качеству" as QA
ДвижокТестирования -> ИнструментСравнения: сравнить(метрикиОригинала, метрикиОптимизированного) participant "Веб-интерфейс" as UI
ИнструментСравнения --> ВебПортал: отчетСравнения participant "Backend-сервис" as Backend
ВебПортал -> ГенераторРекомендаций: сгенерироватьРекомендации(отчетСравнения) participant "Облачная платформа" as Cloud
ГенераторРекомендаций --> ВебПортал: рекомендации participant "Модуль сбора метрик" as Metrics
ВебПортал --> ОблачныйИнженер: отобразить(отчетСравнения, рекомендации)
QA -> UI : Нажать "Запустить тестирование"
UI -> Backend : Передать параметры теста
Backend -> Cloud : Запустить функцию (итерации, параллельность)
Cloud --> Metrics : Передача метрик во время выполнения
Metrics --> Backend : Сбор и агрегация результатов
Backend --> UI : Передать данные для отображения
@enduml
@startuml Sequence_Results
title Диаграмма последовательности: Сравнение результатов
actor "Разработчик" as Dev
participant "Веб-интерфейс" as UI
participant "Backend-сервис" as Backend
Dev -> UI : Выбор двух профилей тестирования
UI -> Backend : Запрос данных двух профилей
Backend --> UI : Данные профилей + дельты
UI --> Dev : Отобразить графики и "Сводку изменений"
@enduml @enduml
\ No newline at end of file
@startuml StateDiagram @startuml State_Editor
title Диаграмма состояний процесса тестирования Serverless title Диаграмма состояний: Редактор кода
[*] --> Создано skinparam state {
Создано --> Оптимизировано : оптимизировать() BackgroundColor #FDF6E3
Оптимизировано --> Выполняется : выполнить() BorderColor #586E75
Выполняется --> МетрикиСобраны : собратьМетрики() }
МетрикиСобраны --> Сравнение : сравнить()
Сравнение --> Рекомендации : сгенерироватьРекомендации() [*] --> Create : Открыть страницу в режиме Create
Рекомендации --> Просмотр : просмотреть()
Просмотр --> [*] state Create {
[*] --> Создание
Создание --> Сохранено : Нажать «Сохранить» (код ≠ пустой)
}
Create --> View : Сохранено → View
state View {
[*] --> Просмотр
Просмотр --> Edit : Нажать «Редактировать»
Просмотр --> ViewOptimized : Нажать «Оптимизировать»
}
state ViewOptimized {
[*] --> Оптимизировано
Оптимизировано --> Edit : Нажать «Редактировать»
}
state Edit {
[*] --> Редактирование
Редактирование --> View : Нажать «Сохранить» (есть изменения)
}
@enduml
@startuml State_Testing
title Диаграмма состояний: Тестирование
skinparam state {
BackgroundColor #EEF6F7
BorderColor #2AA198
}
[*] --> Configuration : Открыть страницу «Тестирование»
state Configuration {
[*] --> Конфигурирование
Конфигурирование --> Testing : Нажать «Запустить тестирование» (все поля заполнены)
}
state Testing {
[*] --> Запущено
Запущено --> Configuration : Отмена пользователя
Запущено --> Results : Тестирование завершено
}
state Results {
[*] --> Отображение
Отображение --> Configuration : Нажать «Новый тест»
Отображение --> ResultsPage : Нажать «Просмотр результатов»
}
@enduml
@startuml State_Results
title Диаграмма состояний: Результаты
skinparam state {
BackgroundColor #ECEBE4
BorderColor #657B83
}
[*] --> Selection : Открыть страницу «Результаты»
state Selection {
[*] --> ОжиданиеВыбора
ОжиданиеВыбора --> View : Выбрать профиль в Селекторе 1
}
state View {
[*] --> Просмотр
Просмотр --> Selection : Сбросить Селектор 1
Просмотр --> Compare : Выбрать профиль в Селекторе 2
}
state Compare {
[*] --> Сравнение
Сравнение --> View : Сбросить Селектор 2
Сравнение --> Selection : Сбросить Селектор 1
}
@enduml @enduml
\ No newline at end of file
@startuml UseCase @startuml UseCase
title Диаграмма вариантов использования комплекса тестирования Serverless title Диаграмма вариантов использования: Тестирование Serverless
left to right direction left to right direction
actor ОблачныйИнженер as "Облачный инженер" skinparam packageStyle rectangle
rectangle "Комплекс тестирования Serverless" {
(Настроить тестовые сценарии) as UC1 actor "Разработчик" as Developer
(Запустить тесты) as UC2 actor "Инженер по качеству" as QA
(Просмотреть результаты) as UC3
(Сгенерировать рекомендации) as UC4 rectangle "Управление функциями" {
} usecase "Создать новую функцию" as UC1
ОблачныйИнженер --> UC1 usecase "Просмотреть список функций" as UC2
ОблачныйИнженер --> UC2 usecase "Редактировать функцию" as UC3
ОблачныйИнженер --> UC3 }
ОблачныйИнженер --> UC4
rectangle "Оптимизация функции" {
usecase "Запустить авто-оптимизацию" as UC4
usecase "Применить рекомендации LLM" as UC5
}
rectangle "Тестирование" {
usecase "Настроить параметры тестирования" as UC6
usecase "Запустить нагрузочное тестирование" as UC7
}
rectangle "Анализ результатов" {
usecase "Просмотреть результаты теста" as UC8
usecase "Сравнить профили тестирования" as UC9
}
Developer --> UC1
Developer --> UC2
Developer --> UC3
Developer --> UC4
Developer --> UC5
QA --> UC6
QA --> UC7
Developer --> UC8
Developer --> UC9
@enduml @enduml
# Node.js
node_modules/
# Logs
logs
*.log
npm-debug.log*
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn
.yarn-integrity
# dotenv environment variables file
.env
# MacOS
.DS_Store
# Editor directories and files
.idea/
.vscode/
*.sublime-workspace
# Build directories
build/
dist/
# Docker
*.container
*.tar
\ No newline at end of file
FROM node:20-alpine
WORKDIR /app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install
# No need to copy source files as they will be mounted as a volume
# Expose port for development server
EXPOSE 3000
# Start development server with hot-reloading
CMD ["npm", "start"]
\ No newline at end of file
...@@ -2,10 +2,18 @@ version: '3.8' ...@@ -2,10 +2,18 @@ version: '3.8'
services: services:
frontend: frontend:
build: . build:
context: .
dockerfile: Dockerfile.dev
ports: ports:
- "8080:80" - "3000:3000"
restart: unless-stopped restart: unless-stopped
volumes:
- ./:/app
- /app/node_modules
environment:
- CHOKIDAR_USEPOLLING=true
- NODE_ENV=development
networks: networks:
- serverless-net - serverless-net
......
This diff is collapsed.
...@@ -6,7 +6,9 @@ ...@@ -6,7 +6,9 @@
"@emotion/react": "^11.11.0", "@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5.11.16", "@mui/icons-material": "^5.11.16",
"@mui/lab": "^5.0.0-alpha.153",
"@mui/material": "^5.13.2", "@mui/material": "^5.13.2",
"@uiw/react-textarea-code-editor": "^3.1.1",
"chart.js": "^4.3.0", "chart.js": "^4.3.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<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/css?family=Roboto:300,400,500,700&display=swap" />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<title>Платформа тестирования Serverless</title> <title>Платформа тестирования Serverless</title>
</head> </head>
<body> <body>
......
\ No newline at end of file
...@@ -4,10 +4,10 @@ import { ThemeProvider, createTheme } from '@mui/material/styles'; ...@@ -4,10 +4,10 @@ import { ThemeProvider, createTheme } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline'; import CssBaseline from '@mui/material/CssBaseline';
import Layout from './components/Layout'; import Layout from './components/Layout';
import HomePage from './pages/HomePage'; import FunctionsPage from './pages/FunctionsPage';
import OptimizationPage from './pages/OptimizationPage';
import TestingPage from './pages/TestingPage'; import TestingPage from './pages/TestingPage';
import ResultsPage from './pages/ResultsPage'; import ResultsPage from './pages/ResultsPage';
import ConfigurationPage from './pages/ConfigurationPage';
const theme = createTheme({ const theme = createTheme({
palette: { palette: {
...@@ -32,10 +32,13 @@ function App() { ...@@ -32,10 +32,13 @@ function App() {
<CssBaseline /> <CssBaseline />
<Layout> <Layout>
<Routes> <Routes>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<FunctionsPage />} />
<Route path="/optimization" element={<OptimizationPage />} />
<Route path="/optimization/:functionId" element={<OptimizationPage />} />
<Route path="/optimization/:functionId/:version" element={<OptimizationPage />} />
<Route path="/testing" element={<TestingPage />} /> <Route path="/testing" element={<TestingPage />} />
<Route path="/testing/:functionId/:version" element={<TestingPage />} />
<Route path="/results" element={<ResultsPage />} /> <Route path="/results" element={<ResultsPage />} />
<Route path="/configuration" element={<ConfigurationPage />} />
</Routes> </Routes>
</Layout> </Layout>
</ThemeProvider> </ThemeProvider>
......
...@@ -15,18 +15,18 @@ import { ...@@ -15,18 +15,18 @@ import {
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import MenuIcon from '@mui/icons-material/Menu'; import MenuIcon from '@mui/icons-material/Menu';
import HomeIcon from '@mui/icons-material/Home'; import FunctionsIcon from '@mui/icons-material/Code';
import BuildIcon from '@mui/icons-material/Build';
import SpeedIcon from '@mui/icons-material/Speed'; import SpeedIcon from '@mui/icons-material/Speed';
import BarChartIcon from '@mui/icons-material/BarChart'; import BarChartIcon from '@mui/icons-material/BarChart';
import SettingsIcon from '@mui/icons-material/Settings';
const drawerWidth = 240; const drawerWidth = 240;
const menuItems = [ const menuItems = [
{ text: 'Главная', icon: <HomeIcon />, path: '/' }, { text: 'Функции', icon: <FunctionsIcon />, path: '/' },
{ text: 'Оптимизация', icon: <BuildIcon />, path: '/optimization' },
{ text: 'Тестирование', icon: <SpeedIcon />, path: '/testing' }, { text: 'Тестирование', icon: <SpeedIcon />, path: '/testing' },
{ text: 'Результаты', icon: <BarChartIcon />, path: '/results' }, { text: 'Результаты', icon: <BarChartIcon />, path: '/results' },
{ text: 'Конфигурация', icon: <SettingsIcon />, path: '/configuration' },
]; ];
function Layout({ children }) { function Layout({ children }) {
...@@ -40,11 +40,18 @@ function Layout({ children }) { ...@@ -40,11 +40,18 @@ function Layout({ children }) {
setMobileOpen(!mobileOpen); setMobileOpen(!mobileOpen);
}; };
const isPathActive = (path) => {
if (path === '/') {
return location.pathname === '/';
}
return location.pathname.startsWith(path);
};
const drawer = ( const drawer = (
<div> <div>
<Toolbar sx={{ justifyContent: 'center' }}> <Toolbar sx={{ justifyContent: 'center' }}>
<Typography variant="h6" component="div" fontWeight="bold"> <Typography variant="h6" component="div" fontWeight="bold">
ServerlessTest Serverless Optimizer
</Typography> </Typography>
</Toolbar> </Toolbar>
<List> <List>
...@@ -53,7 +60,7 @@ function Layout({ children }) { ...@@ -53,7 +60,7 @@ function Layout({ children }) {
button button
key={item.text} key={item.text}
onClick={() => navigate(item.path)} onClick={() => navigate(item.path)}
selected={location.pathname === item.path} selected={isPathActive(item.path)}
> >
<ListItemIcon>{item.icon}</ListItemIcon> <ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={item.text} /> <ListItemText primary={item.text} />
...@@ -83,7 +90,7 @@ function Layout({ children }) { ...@@ -83,7 +90,7 @@ function Layout({ children }) {
<MenuIcon /> <MenuIcon />
</IconButton> </IconButton>
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}> <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
Тестирование Serverless-функций Оптимизация выполнения serverless-функций
</Typography> </Typography>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
......
...@@ -25,3 +25,34 @@ code { ...@@ -25,3 +25,34 @@ code {
height: 300px; height: 300px;
position: relative; position: relative;
} }
/* Code Editor Styles */
.w-tc-editor {
position: relative;
}
.line-numbers > div {
height: 21px; /* Match the line-height of the editor */
}
/* Ensure editor lines match height with line numbers */
.w-tc-editor pre {
line-height: 1.5;
}
/* Ensure consistent scrolling */
.w-tc-editor-text {
overflow-y: auto !important;
}
/* Enforce JetBrains Mono for the editor */
.w-tc-editor-text,
.w-tc-editor-preview {
font-family: 'JetBrains Mono', monospace !important;
}
/* Dark mode support */
.w-tc-editor[data-color-mode="dark"] .w-tc-editor-text > div::before {
color: #666;
border-right-color: #444;
}
\ No newline at end of file
...@@ -14,4 +14,7 @@ root.render( ...@@ -14,4 +14,7 @@ root.render(
</React.StrictMode> </React.StrictMode>
); );
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals(); reportWebVitals();
\ No newline at end of file
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box,
Button,
Container,
Divider,
IconButton,
List,
ListItem,
ListItemButton,
ListItemIcon,
ListItemText,
Modal,
Paper,
Tooltip,
Typography,
Collapse,
} from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
import CodeIcon from '@mui/icons-material/Code';
import JavascriptIcon from '@mui/icons-material/Javascript';
import CloudIcon from '@mui/icons-material/Cloud';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import VisibilityIcon from '@mui/icons-material/Visibility';
import SpeedIcon from '@mui/icons-material/Speed';
import Chip from '@mui/material/Chip';
// Моковые данные для отображения
const mockFunctions = [
{
id: 'func1',
name: 'API Gateway Handler',
language: 'python',
platform: 'yandex',
versions: [
{ version: 1, type: 'manual', createdAt: '2024-03-10' },
{ version: 2, type: 'auto', createdAt: '2024-03-11' },
{ version: 3, type: 'manual', createdAt: '2024-03-12' }
]
},
{
id: 'func2',
name: 'Database Worker',
language: 'nodejs',
platform: 'yandex',
versions: [
{ version: 1, type: 'manual', createdAt: '2024-03-14' },
{ version: 2, type: 'auto', createdAt: '2024-03-15' }
]
},
{
id: 'func3',
name: 'Image Processor',
language: 'python',
platform: 'yandex',
versions: [
{ version: 1, type: 'manual', createdAt: '2024-03-20' }
]
}
];
const modalStyle = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
borderRadius: 1,
boxShadow: 24,
p: 4,
};
function FunctionsPage() {
const navigate = useNavigate();
const [expandedFunctions, setExpandedFunctions] = useState({});
const [selectedVersion, setSelectedVersion] = useState(null);
const [modalOpen, setModalOpen] = useState(false);
const toggleFunction = (id) => {
setExpandedFunctions(prev => ({
...prev,
[id]: !prev[id]
}));
};
const openVersionModal = (func, version) => {
setSelectedVersion({ functionId: func.id, functionName: func.name, ...version });
setModalOpen(true);
};
const closeModal = () => {
setModalOpen(false);
};
const handleViewClick = () => {
navigate(`/optimization/${selectedVersion.functionId}/${selectedVersion.version}`);
closeModal();
};
const handleTestClick = () => {
navigate(`/testing/${selectedVersion.functionId}/${selectedVersion.version}`);
closeModal();
};
const getLanguageIcon = (language) => {
switch (language) {
case 'python':
return <CodeIcon fontSize="small" color="primary" />;
case 'nodejs':
return <JavascriptIcon fontSize="small" color="warning" />;
default:
return <CodeIcon fontSize="small" />;
}
};
const getPlatformIcon = (platform) => {
return <CloudIcon fontSize="small" color="info" />;
};
const getVersionTypeChip = (type) => {
if (type === 'auto') {
return <Chip label="Auto" size="small" color="secondary" variant="outlined" sx={{ ml: 1 }} />;
}
return <Chip label="Manual" size="small" color="primary" variant="outlined" sx={{ ml: 1 }} />;
};
return (
<Container maxWidth="lg">
<Box sx={{ my: 4 }}>
<Typography variant="h4" component="h1" gutterBottom>
Сохраненные функции
</Typography>
<Paper elevation={2} sx={{ p: 0, mb: 4 }}>
<List sx={{ width: '100%' }}>
{mockFunctions.map((func) => (
<React.Fragment key={func.id}>
<ListItem
secondaryAction={
<IconButton edge="end" onClick={() => toggleFunction(func.id)}>
{expandedFunctions[func.id] ? <ExpandLessIcon /> : <ExpandMoreIcon />}
</IconButton>
}
disablePadding
>
<ListItemButton onClick={() => toggleFunction(func.id)}>
<ListItemIcon>
{getLanguageIcon(func.language)}
</ListItemIcon>
<ListItemText
primary={func.name}
primaryTypographyProps={{ fontWeight: 'medium' }}
/>
<Box sx={{ display: 'flex', alignItems: 'center', mr: 2 }}>
<Tooltip title={func.language === 'python' ? 'Python' : 'Node.js'}>
{getLanguageIcon(func.language)}
</Tooltip>
<Box sx={{ mx: 0.5 }} />
<Tooltip title="Yandex Cloud Functions">
{getPlatformIcon(func.platform)}
</Tooltip>
</Box>
</ListItemButton>
</ListItem>
<Collapse in={expandedFunctions[func.id]} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{func.versions.map((version) => (
<ListItem
key={`${func.id}-v${version.version}`}
sx={{ pl: 4 }}
secondaryAction={
<IconButton edge="end" onClick={() => openVersionModal(func, version)}>
<VisibilityIcon fontSize="small" />
</IconButton>
}
>
<ListItemButton onClick={() => openVersionModal(func, version)}>
<ListItemText
primary={`Версия ${version.version}`}
secondary={version.createdAt}
/>
{getVersionTypeChip(version.type)}
</ListItemButton>
</ListItem>
))}
</List>
</Collapse>
<Divider component="li" />
</React.Fragment>
))}
</List>
</Paper>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={() => navigate('/optimization')}
sx={{ mb: 4 }}
>
Новая функция
</Button>
<Modal
open={modalOpen}
onClose={closeModal}
aria-labelledby="version-modal-title"
>
<Box sx={modalStyle}>
<Typography id="version-modal-title" variant="h6" component="h2" gutterBottom>
{selectedVersion && `${selectedVersion.functionName} - Версия ${selectedVersion.version}`}
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
{selectedVersion && `Создана: ${selectedVersion.createdAt}`}
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mt: 3 }}>
<Button
variant="outlined"
startIcon={<VisibilityIcon />}
onClick={handleViewClick}
>
Просмотр
</Button>
<Button
variant="contained"
startIcon={<SpeedIcon />}
onClick={handleTestClick}
>
Тестирование
</Button>
</Box>
</Box>
</Modal>
</Box>
</Container>
);
}
export default FunctionsPage;
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment