Skip to content

Commit f7bb600

Browse files
author
txix
committed
2026-02-02 12:22:08: merge [main:db69e8e86b6668b0bf201a7bc38edff13dc3b35a];
1 parent 4aa6aec commit f7bb600

17 files changed

Lines changed: 2057 additions & 110 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 1.7.0
2+
- Добавлено
3+
- Страница Swagger для модулей
14
## 1.6.5
25
- Обновлено
36
- библиотека isp-ui-kit до версии 1.0.0

package-lock.json

Lines changed: 1768 additions & 65 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "isp-admin-ui-kit",
3-
"version": "1.6.5",
3+
"version": "1.7.0",
44
"main": "dist/index.js",
55
"type": "module",
66
"scripts": {
@@ -55,6 +55,7 @@
5555
"@types/react-dom": "^18.2.24",
5656
"@types/react-resizable": "^3.0.7",
5757
"@types/react-router-dom": "^5.3.3",
58+
"@types/swagger-ui-react": "^5.18.0",
5859
"@typescript-eslint/eslint-plugin": "^7.5.0",
5960
"@typescript-eslint/parser": "^7.5.0",
6061
"eslint": "^8.57.0",
@@ -90,7 +91,8 @@
9091
"react-dom": ">=18.2.0",
9192
"react-hook-form": "^7.54.0",
9293
"react-redux": "^9.1.0",
93-
"react-router-dom": "^6.22.3"
94+
"react-router-dom": "^6.22.3",
95+
"swagger-ui-react": "^5.31.0"
9496
},
9597
"eslintConfig": {
9698
"extends": [

src/components/ModuleTabs/index.tsx

Lines changed: 64 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { Tabs } from 'antd'
2-
import { memo } from 'react'
2+
import { memo, useEffect, useMemo } from 'react'
33
import { useNavigate, useParams } from 'react-router-dom'
44

5+
import { MODULES_TAB_KEYS } from '@constants/modules.ts'
6+
57
import ModuleTabContent from '@widgets/ModuleTabContent'
68

79
import { ModuleTabsPropsType } from '@components/ModuleTabs/module-tabs.type.ts'
810

911
const secondColumnItems = [
10-
{
11-
key: 'configurations',
12-
name: 'Конфигурации'
13-
},
14-
{ key: 'connections', name: 'Подключения' }
15-
]
12+
{ key: 'configurations', name: 'Конфигурации' },
13+
{ key: 'connections', name: 'Подключения' },
14+
{ key: 'swagger', name: 'Swagger' }
15+
] as const
1616

1717
const ModuleTabs = ({
1818
activeTab,
@@ -22,37 +22,66 @@ const ModuleTabs = ({
2222
}: ModuleTabsPropsType) => {
2323
const navigate = useNavigate()
2424
const { id: selectedItemId = '' } = useParams()
25+
26+
const hasSwagger = useMemo(
27+
() =>
28+
currentModule?.status?.some((status) =>
29+
status.endpoints?.some((e) => e.path.includes('swagger'))
30+
),
31+
[currentModule]
32+
)
33+
34+
const resolvedActiveTab = useMemo(() => {
35+
return activeTab === MODULES_TAB_KEYS.swagger && !hasSwagger
36+
? 'configurations'
37+
: activeTab
38+
}, [activeTab, hasSwagger])
39+
40+
const navigateToTab = (tabKey: string) => {
41+
const params = new URLSearchParams(searchParams)
42+
navigate(
43+
{
44+
pathname: `${selectedItemId}/${tabKey}`,
45+
search: params.toString()
46+
},
47+
{ replace: true }
48+
)
49+
}
50+
51+
useEffect(() => {
52+
if (resolvedActiveTab !== activeTab) {
53+
setActiveTab(resolvedActiveTab)
54+
navigateToTab(resolvedActiveTab)
55+
}
56+
}, [resolvedActiveTab])
57+
58+
const items = useMemo(
59+
() =>
60+
secondColumnItems.filter(({ key }) => key !== 'swagger' || hasSwagger),
61+
[hasSwagger]
62+
)
63+
2564
return (
2665
<Tabs
27-
activeKey={activeTab}
28-
onChange={(activeKey) => {
29-
setActiveTab(activeKey)
30-
const params = new URLSearchParams(searchParams)
31-
const path = `${selectedItemId}/${activeKey}`
32-
navigate(
33-
{
34-
pathname: path,
35-
search: params.toString()
36-
},
37-
{ replace: true }
38-
)
39-
}}
66+
activeKey={resolvedActiveTab}
4067
className="modules-page__tabs"
41-
items={secondColumnItems.map((item) => {
42-
return {
43-
disabled: selectedItemId === '',
44-
label: item.name,
45-
key: item.key,
46-
children: (
47-
<div className="modules-page__content">
48-
<ModuleTabContent
49-
selectedItemId={selectedItemId}
50-
currentModule={currentModule}
51-
/>
52-
</div>
53-
)
54-
}
55-
})}
68+
onChange={(key) => {
69+
setActiveTab(key)
70+
navigateToTab(key)
71+
}}
72+
items={items.map(({ key, name }) => ({
73+
key,
74+
label: name,
75+
disabled: !selectedItemId,
76+
children: (
77+
<div className="modules-page__content">
78+
<ModuleTabContent
79+
selectedItemId={selectedItemId}
80+
currentModule={currentModule}
81+
/>
82+
</div>
83+
)
84+
}))}
5685
/>
5786
)
5887
}

src/constants/api/apiPaths.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ type PathKeys =
7272
| 'updateApplicationGroup'
7373

7474
const BASE_URL = import.meta.env.BASE_URL?.replace(/\/$/, '') || ''
75-
const API_BASE_URL = `${BASE_URL}/api`
75+
export const API_BASE_URL = `${BASE_URL}/api`
7676
const apiWithBaseURL = (path: `/${string}`) => `${API_BASE_URL}${path}`
7777

7878
export const apiPaths: Record<PathKeys, string> = {

src/constants/modules.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export type ModulesTabKeysType = 'configurations' | 'connections' | 'swagger'
2+
3+
export const MODULES_TAB_KEYS: Record<ModulesTabKeysType, string> = {
4+
configurations: 'configurations',
5+
connections: 'connections',
6+
swagger: 'swagger'
7+
}

src/pages/ModulesPage/index.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
useSearchParams
1111
} from 'react-router-dom'
1212

13+
import { MODULES_TAB_KEYS, ModulesTabKeysType } from '@constants/modules.ts'
14+
1315
import ModuleTabs from '@components/ModuleTabs'
1416

1517
import { ModuleType } from '@pages/ModulesPage/module.type.ts'
@@ -28,7 +30,7 @@ import { PermissionKeysType } from '@type/roles.type.ts'
2830
import './modules-page.scss'
2931

3032
const ModulesPage = () => {
31-
const [activeTab, setActiveTab] = useState('configurations')
33+
const [activeTab, setActiveTab] = useState(MODULES_TAB_KEYS.configurations)
3234
const { id: selectedItemId = '' } = useParams()
3335
const navigate = useNavigate()
3436
const { role, hasPermission } = useRole()
@@ -64,12 +66,14 @@ const ModulesPage = () => {
6466
useEffect(() => {
6567
const url = location.pathname.substring(
6668
location.pathname.lastIndexOf('/') + 1
67-
)
69+
) as ModulesTabKeysType
6870
switch (url) {
69-
case 'configurations':
70-
return setActiveTab('configurations')
71-
case 'connections':
72-
return setActiveTab('connections')
71+
case MODULES_TAB_KEYS.configurations:
72+
return setActiveTab(MODULES_TAB_KEYS.configurations)
73+
case MODULES_TAB_KEYS.connections:
74+
return setActiveTab(MODULES_TAB_KEYS.connections)
75+
case MODULES_TAB_KEYS.swagger:
76+
return setActiveTab(MODULES_TAB_KEYS.swagger)
7377
}
7478
}, [location])
7579

@@ -179,7 +183,6 @@ const ModulesPage = () => {
179183
</List.Item>
180184
)
181185
}
182-
183186
return (
184187
<section className="modules-page">
185188
<Column

src/pages/SwaggerPage/index.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { Button, Spin } from 'antd'
2+
import { useEffect, useState } from 'react'
3+
import { useParams } from 'react-router-dom'
4+
import SwaggerUI from 'swagger-ui-react'
5+
6+
import { base64ToUtf8 } from '@utils/base64ToJson.ts'
7+
import { downloadFile } from '@utils/downloadFile.ts'
8+
import { toSafeFileName } from '@utils/toSafeFileName.ts'
9+
10+
import modulesServiceApi from '@services/modulesService.ts'
11+
import swaggerServiceApi from '@services/swaggerService.ts'
12+
13+
import './swagger-page.scss'
14+
15+
import 'swagger-ui-react/swagger-ui.css'
16+
17+
const SwaggerPage = () => {
18+
const { id } = useParams<{ id: string }>()
19+
20+
const { data: modules = [] } = modulesServiceApi.useGetModulesQuery('modules')
21+
22+
const [swaggerPath, setSwaggerPath] = useState<string>('')
23+
const [moduleName, setModuleName] = useState<string>('module')
24+
25+
useEffect(() => {
26+
if (!modules.length || !id) {
27+
return
28+
}
29+
30+
const module = modules.find((m) => m.id === id)
31+
32+
if (!module) {
33+
return
34+
}
35+
setModuleName(module.name ?? 'module')
36+
37+
const swaggerEndpoint = module.status
38+
?.flatMap((s) => s.endpoints)
39+
?.find((e) => e?.path?.includes('swagger'))
40+
41+
if (swaggerEndpoint) {
42+
setSwaggerPath(`/${swaggerEndpoint.path}`)
43+
}
44+
}, [modules, id])
45+
46+
const { data: swaggerSpec, isLoading } = swaggerServiceApi.useGetSwaggerQuery(
47+
swaggerPath,
48+
{
49+
skip: !swaggerPath
50+
}
51+
)
52+
53+
const swaggerSpecText = swaggerSpec ? base64ToUtf8(swaggerSpec) : ''
54+
55+
const handleDownload = () => {
56+
if (!swaggerSpecText) {
57+
return
58+
}
59+
60+
const safeName = toSafeFileName(moduleName)
61+
62+
downloadFile({
63+
fileName: `swagger-${safeName}.yml`,
64+
content: swaggerSpecText,
65+
mimeType: 'application/yaml;charset=utf-8'
66+
})
67+
}
68+
69+
if (isLoading) {
70+
return (
71+
<div className="swagger-page__loader">
72+
<Spin size="large" />
73+
</div>
74+
)
75+
}
76+
77+
return (
78+
<div className="swagger-page">
79+
{swaggerSpec ? (
80+
<>
81+
<div className="swagger-page__toolbar">
82+
<Button type="primary" onClick={handleDownload}>
83+
Скачать Swagger
84+
</Button>
85+
</div>
86+
87+
<div className="swagger-page__content">
88+
<SwaggerUI spec={swaggerSpecText} />
89+
</div>
90+
</>
91+
) : (
92+
<div className="swagger-page__no-data">Нет данных Swagger</div>
93+
)}
94+
</div>
95+
)
96+
}
97+
98+
export default SwaggerPage
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.swagger-page {
2+
height: calc(100vh - 100px);
3+
overflow: hidden;
4+
display: flex;
5+
flex-direction: column;
6+
width: 100%;
7+
8+
&__toolbar {
9+
padding: 12px 16px;
10+
background: #f7f7f7;
11+
border-bottom: 1px solid #e5e5e5;
12+
}
13+
14+
&__content {
15+
flex: 1;
16+
min-height: 0;
17+
display: flex;
18+
}
19+
20+
&__content .swagger-ui {
21+
flex: 1;
22+
overflow-y: auto;
23+
background: #fff;
24+
}
25+
}

src/routes/Routers/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import LayoutComponent from '@components/Layout'
88
import Configurations from '@pages/ConfigurationsPage'
99
import Connections from '@pages/ConnectionsPage'
1010
import ErrorWrapperPage from '@pages/ErrorWrapperPage'
11+
import SwaggerPage from '@pages/SwaggerPage'
1112

1213
import PrivateRoute from '@routes/PrivateRoute'
1314
import { generateCustomRoutes } from '@routes/customRoutes.tsx'
@@ -140,6 +141,10 @@ export const getRoutesConfig = (
140141
{
141142
path: routePaths.connections,
142143
element: lazyElement(Connections)
144+
},
145+
{
146+
path: routePaths.swagger,
147+
element: lazyElement(SwaggerPage)
143148
}
144149
]
145150
}

0 commit comments

Comments
 (0)