diff --git a/react-ui/.gitignore b/react-ui/.gitignore index 5ec5a7340dbd9f3ea839640766825c59ed64d81a..2773d47afe0134a1f9fd5dfc588945a527b3b9ed 100755 --- a/react-ui/.gitignore +++ b/react-ui/.gitignore @@ -8,6 +8,7 @@ # testing /coverage +/tmp # api /src/api/** diff --git a/react-ui/docker/webserver/Dockerfile b/react-ui/docker/webserver/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..f4644ee1036b55d30673e0290d3a89aec8a68c60 --- /dev/null +++ b/react-ui/docker/webserver/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:alpine3.20 + +COPY dist /usr/share/nginx/html +COPY docker/webserver/nginx.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/react-ui/docker/webserver/nginx.conf b/react-ui/docker/webserver/nginx.conf new file mode 100644 index 0000000000000000000000000000000000000000..87066f6ab89b1a840816f93fedc989e0d0736bfb --- /dev/null +++ b/react-ui/docker/webserver/nginx.conf @@ -0,0 +1,154 @@ + +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; + + resolver 127.0.0.11 ipv6=off; + + #gzip on; + + server { + listen 80; + server_name localhost; + + #charset koi8-r; + + #access_log logs/host.access.log main; + + + location ^~ /api/ { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # CORS headers + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always; + add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always; + + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + + # Remove /api prefix when proxying + rewrite ^/api/(.*) /$1 break; + proxy_pass http://clab-gosdn_csbi_arista_base-gosdn:8080; + } + + location / { + root /usr/share/nginx/html; + index index.html; + try_files $uri $uri/ /index.html; + } + + location ~* \.(js|css|jpg|png|svg|woff|woff2|ttf|otf|eot|ico)$ { + root /usr/share/nginx/html; + add_header 'Access-Control-Allow-Origin' '*' always; + expires 30d; + add_header Cache-Control "public"; + } + + # #error_page 404 /404.html; + + # # redirect server error pages to the static page /50x.html + # # + # error_page 500 502 503 504 /50x.html; + # location = /50x.html { + # root html; + # } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} + } + + + # another virtual host using mix of IP-, name-, and port-based configuration + # + #server { + # listen 8000; + # listen somename:8080; + # server_name somename alias another.alias; + + # location / { + # root html; + # index index.html index.htm; + # } + #} + + + # HTTPS server + # + #server { + # listen 443 ssl; + # server_name localhost; + + # ssl_certificate cert.pem; + # ssl_certificate_key cert.key; + + # ssl_session_cache shared:SSL:1m; + # ssl_session_timeout 5m; + + # ssl_ciphers HIGH:!aNULL:!MD5; + # ssl_prefer_server_ciphers on; + + # location / { + # root html; + # index index.html index.htm; + # } + #} + +} \ No newline at end of file diff --git a/react-ui/package.json b/react-ui/package.json index 466870928565852b6973caf2b581e1ed9afb4abf..709c5bfcb623cd76f6b0e5264378ad3f909a58a7 100755 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -1,6 +1,10 @@ { "name": "react-ui", "version": "0.1.0", + "author": { + "name": "Matthias Feyll", + "email": "matthias.feyll@stud.h-da.de" + }, "private": true, "type": "module", "dependencies": { @@ -29,17 +33,15 @@ }, "scripts": { "start": "vite", - "build": "tsc && vite build", - "test": "react-scripts test", + "build::frontend": "vite build", "build::api": "npx @rtk-query/codegen-openapi ./scripts/openapi-config.json", + "build": "yarn build::api && yarn build::frontend", "lint": "eslint src", - "lint::fix": "eslint src --fix", - "clean": "./scripts/clean.sh" + "lint::fix": "eslint src --fix" }, "eslintConfig": { "extends": [ - "react-app", - "react-app/jest" + "react-app" ] }, "browserslist": { @@ -54,7 +56,6 @@ "last 1 safari version" ] }, - "proxy": "http://localhost:55055", "devDependencies": { "@babel/runtime": "^7.21.5", "@rtk-query/codegen-openapi": "^2.0.0", @@ -84,4 +85,4 @@ "vite": "^6.0.3", "vite-bundle-visualizer": "^1.2.1" } -} +} \ No newline at end of file diff --git a/react-ui/scripts/build.sh b/react-ui/scripts/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..ad0f0cd0b20382110f1dbff59206f95c1d5a14f2 --- /dev/null +++ b/react-ui/scripts/build.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env sh + +IMAGE=node:23-alpine3.20 + +docker run --rm \ + -v $(pwd):/app \ + -w /app \ + -u $(id -u):$(id -g) \ + $IMAGE \ + yarn install && yarn build + +if [ $? -ne 0 ]; then + echo "Error while building frontend app" + exit 1 +fi + +echo "Build successfully in $(pwd)/dist" diff --git a/react-ui/scripts/clean.sh b/react-ui/scripts/clean.sh deleted file mode 100755 index 4220321a8f4f8ab460fd472217d3c93e7aabb01c..0000000000000000000000000000000000000000 --- a/react-ui/scripts/clean.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env sh - -docker rmi react-ui-cpp-build - -rm -rf ./build \ No newline at end of file diff --git a/react-ui/src/shared/api/api.ts b/react-ui/src/shared/api/api.ts index c964d9dc98ad7b63bb1a7e118a28fbffc310ef8c..32969f1686479812253d387868a9350bd41a882c 100755 --- a/react-ui/src/shared/api/api.ts +++ b/react-ui/src/shared/api/api.ts @@ -34,7 +34,9 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/export/${queryArg.pid}`, - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), providesTags: ['ConfigurationManagementService'], }), @@ -52,7 +54,10 @@ const injectedRtkApi = api }), invalidatesTags: ['ConfigurationManagementService'], }), - authServiceLogin: build.mutation<AuthServiceLoginApiResponse, AuthServiceLoginApiArg>({ + authServiceLogin: build.mutation< + AuthServiceLoginApiResponse, + AuthServiceLoginApiArg + >({ query: (queryArg) => ({ url: `/login`, method: 'POST', @@ -67,7 +72,9 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/logout/${queryArg.username}`, method: 'POST', - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), invalidatesTags: ['AuthService'], }), @@ -77,7 +84,10 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/mnes`, - params: { timestamp: queryArg.timestamp, pid: queryArg.pid }, + params: { + timestamp: queryArg.timestamp, + pid: queryArg.pid, + }, }), providesTags: ['NetworkElementService'], }), @@ -98,7 +108,10 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/mnes/changes`, - params: { timestamp: queryArg.timestamp, pid: queryArg.pid }, + params: { + timestamp: queryArg.timestamp, + pid: queryArg.pid, + }, }), providesTags: ['NetworkElementService'], }), @@ -119,7 +132,10 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/mnes/changes/${queryArg.cuid}`, - params: { timestamp: queryArg.timestamp, pid: queryArg.pid }, + params: { + timestamp: queryArg.timestamp, + pid: queryArg.pid, + }, }), providesTags: ['NetworkElementService'], }), @@ -140,7 +156,10 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/mnes/${queryArg.mneid}`, - params: { timestamp: queryArg.timestamp, pid: queryArg.pid }, + params: { + timestamp: queryArg.timestamp, + pid: queryArg.pid, + }, }), providesTags: ['NetworkElementService'], }), @@ -151,7 +170,10 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/mnes/${queryArg.mneid}`, method: 'DELETE', - params: { timestamp: queryArg.timestamp, pid: queryArg.pid }, + params: { + timestamp: queryArg.timestamp, + pid: queryArg.pid, + }, }), invalidatesTags: ['NetworkElementService'], }), @@ -161,7 +183,10 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/mnes/${queryArg.mneid}/intendedpaths/${queryArg.intendedPath}`, - params: { timestamp: queryArg.timestamp, pid: queryArg.pid }, + params: { + timestamp: queryArg.timestamp, + pid: queryArg.pid, + }, }), providesTags: ['NetworkElementService'], }), @@ -171,7 +196,10 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/mnes/${queryArg.mneid}/paths/${queryArg.path}`, - params: { timestamp: queryArg.timestamp, pid: queryArg.pid }, + params: { + timestamp: queryArg.timestamp, + pid: queryArg.pid, + }, }), providesTags: ['NetworkElementService'], }), @@ -186,10 +214,15 @@ const injectedRtkApi = api }), invalidatesTags: ['NetworkElementService'], }), - pndServiceGetPnd: build.query<PndServiceGetPndApiResponse, PndServiceGetPndApiArg>({ + pndServiceGetPnd: build.query< + PndServiceGetPndApiResponse, + PndServiceGetPndApiArg + >({ query: (queryArg) => ({ url: `/pnd/${queryArg.pid}`, - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), providesTags: ['PndService'], }), @@ -197,7 +230,12 @@ const injectedRtkApi = api PndServiceGetPndListApiResponse, PndServiceGetPndListApiArg >({ - query: (queryArg) => ({ url: `/pnds`, params: { timestamp: queryArg.timestamp } }), + query: (queryArg) => ({ + url: `/pnds`, + params: { + timestamp: queryArg.timestamp, + }, + }), providesTags: ['PndService'], }), pndServiceCreatePndList: build.mutation< @@ -218,7 +256,9 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/pnds/${queryArg.pid}`, method: 'DELETE', - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), invalidatesTags: ['PndService'], }), @@ -237,7 +277,12 @@ const injectedRtkApi = api RoleServiceGetRolesApiResponse, RoleServiceGetRolesApiArg >({ - query: (queryArg) => ({ url: `/roles`, params: { timestamp: queryArg.timestamp } }), + query: (queryArg) => ({ + url: `/roles`, + params: { + timestamp: queryArg.timestamp, + }, + }), providesTags: ['RoleService'], }), roleServiceCreateRoles: build.mutation< @@ -258,7 +303,10 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/roles/delete`, method: 'DELETE', - params: { timestamp: queryArg.timestamp, roleName: queryArg.roleName }, + params: { + timestamp: queryArg.timestamp, + roleName: queryArg.roleName, + }, }), invalidatesTags: ['RoleService'], }), @@ -308,7 +356,9 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/routing`, - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), providesTags: ['RoutingTableService'], }), @@ -330,7 +380,10 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/routing/delete`, method: 'DELETE', - params: { timestamp: queryArg.timestamp, id: queryArg.id }, + params: { + timestamp: queryArg.timestamp, + id: queryArg.id, + }, }), invalidatesTags: ['RoutingTableService'], }), @@ -340,7 +393,9 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/submanagement/reset`, - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), providesTags: ['SubscriptionManagementService'], }), @@ -350,7 +405,9 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/submanagement/subscriptions`, - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), providesTags: ['SubscriptionManagementService'], }), @@ -371,7 +428,9 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/submanagement/${queryArg.subid}`, - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), providesTags: ['SubscriptionManagementService'], }), @@ -382,7 +441,9 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/submanagement/${queryArg.subid}`, method: 'DELETE', - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), invalidatesTags: ['SubscriptionManagementService'], }), @@ -392,7 +453,9 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/topology`, - params: { timestamp: queryArg.timestamp }, + params: { + timestamp: queryArg.timestamp, + }, }), providesTags: ['TopologyService'], }), @@ -414,7 +477,10 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/topology/delete`, method: 'DELETE', - params: { timestamp: queryArg.timestamp, id: queryArg.id }, + params: { + timestamp: queryArg.timestamp, + id: queryArg.id, + }, }), invalidatesTags: ['TopologyService'], }), @@ -433,7 +499,12 @@ const injectedRtkApi = api UserServiceGetUsersApiResponse, UserServiceGetUsersApiArg >({ - query: (queryArg) => ({ url: `/users`, params: { timestamp: queryArg.timestamp } }), + query: (queryArg) => ({ + url: `/users`, + params: { + timestamp: queryArg.timestamp, + }, + }), providesTags: ['UserService'], }), userServiceCreateUsers: build.mutation< @@ -454,7 +525,10 @@ const injectedRtkApi = api query: (queryArg) => ({ url: `/users/delete`, method: 'DELETE', - params: { timestamp: queryArg.timestamp, username: queryArg.username }, + params: { + timestamp: queryArg.timestamp, + username: queryArg.username, + }, }), invalidatesTags: ['UserService'], }), @@ -464,7 +538,11 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/users/get`, - params: { timestamp: queryArg.timestamp, name: queryArg.name, id: queryArg.id }, + params: { + timestamp: queryArg.timestamp, + name: queryArg.name, + id: queryArg.id, + }, }), providesTags: ['UserService'], }), @@ -485,7 +563,10 @@ const injectedRtkApi = api >({ query: (queryArg) => ({ url: `/yang/parse`, - params: { timestamp: queryArg.timestamp, yang: queryArg.yang }, + params: { + timestamp: queryArg.timestamp, + yang: queryArg.yang, + }, }), providesTags: ['NetworkElementService'], }), @@ -513,7 +594,8 @@ export type ConfigurationManagementServiceImportSdnConfigApiArg = { timestamp?: string sdnConfigData?: string } -export type AuthServiceLoginApiResponse = /** status 200 A successful response. */ RbacLoginResponse +export type AuthServiceLoginApiResponse = + /** status 200 A successful response. */ RbacLoginResponse export type AuthServiceLoginApiArg = { rbacLoginRequest: Login } @@ -598,7 +680,8 @@ export type NetworkElementServiceUpdateApiResponse = export type NetworkElementServiceUpdateApiArg = { networkelementUpdateNetworkElementRequest: TodoChangeNameToFitTheRest } -export type PndServiceGetPndApiResponse = /** status 200 A successful response. */ PndGetPndResponse +export type PndServiceGetPndApiResponse = + /** status 200 A successful response. */ PndGetPndResponse export type PndServiceGetPndApiArg = { pid: string /** Timestamp in nanoseconds since Epoch. */ @@ -867,10 +950,11 @@ export type TransportGnmiTransportOption = { } export type TransportRestconfTransportOption = object export type ChangedAccordingToStyleGuideHttpsDocsBufBuildBestPracticesStyleGuideEnums = - | 'TYPE_UNSPECIFIED' - | 'TYPE_OPENCONFIG' - | 'TYPE_CONTAINERISED' - | 'TYPE_PLUGIN' + + | 'TYPE_UNSPECIFIED' + | 'TYPE_OPENCONFIG' + | 'TYPE_CONTAINERISED' + | 'TYPE_PLUGIN' export type TransportTransportOption = { address?: string username?: string diff --git a/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx b/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx index da78528f27ffa6adf9b252cb4ba53476949b927d..6c3ac78f306d0ff0b1c09263a4d791795757b51c 100644 --- a/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx +++ b/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx @@ -18,8 +18,6 @@ interface JsonViewerViewModelType { container: React.RefObject<HTMLElement> } -export const MARKED = "?marked" - export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelType) => { const { breadcrumbs, collapseContainer } = useAppSelector(state => state.json_viwer) const dispatch = useAppDispatch(); @@ -128,7 +126,7 @@ export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelTy return found } - const parameterizedJson = useMemo<MutableRefObject<Array<string>>>(() => { + const parameterizedJson = useMemo<MutableRefObject<string[]>>(() => { parameterizedJsonMap.current = [] if (searchTerm === "") { return json