Compare commits

...

71 Commits

Author SHA1 Message Date
Arslan, Erdem
102e38988c enable debug mode for release pipeline 2024-01-30 12:48:57 +01:00
Arslan, Erdem
4e7c44eeb5 update tags for release pipeline 2024-01-30 12:42:27 +01:00
Arslan, Erdem
3238cdf36d update release pipeline 2024-01-30 12:23:14 +01:00
Arslan, Erdem
1abbc383ec create .gitlab-ci.yml for a release pipeline 2024-01-30 12:22:54 +01:00
5a09cc8e31 update ui-assets.nix
All checks were successful
checks-impure / test (push) Has been skipped
checks / test (push) Has been skipped
assets1 / test (push) Has been skipped
2024-01-29 20:02:50 +00:00
Sara Pervana
c6e1b8a21d Merge pull request 'added dark mode switch for icon buttons' (#77) from dark-mode-buttons into main
Some checks failed
checks-impure / test (push) Successful in 26s
checks / test (push) Successful in 1m13s
assets1 / test (push) Has been cancelled
Reviewed-on: #77
2024-01-29 21:00:33 +01:00
sara-pervana
afe291c54c added dark mode switch for icon buttons
All checks were successful
checks-impure / test (pull_request) Successful in 28s
checks / test (pull_request) Successful in 3m47s
2024-01-29 20:53:16 +01:00
6024090f69 update ui-assets.nix
All checks were successful
checks-impure / test (push) Has been skipped
checks / test (push) Has been skipped
assets1 / test (push) Has been skipped
2024-01-29 18:40:49 +00:00
Sara Pervana
596e87c31b Merge pull request 'Adding Consume Functionality' (#74) from consume-functionality into main
Some checks failed
checks-impure / test (push) Successful in 26s
checks / test (push) Successful in 1m10s
assets1 / test (push) Has been cancelled
Reviewed-on: #74
2024-01-29 19:38:27 +01:00
sara-pervana
06c55f151b fixed invalid tailwind classes 2024-01-29 19:38:27 +01:00
sara-pervana
04675f5e9e added a bit of time delay for register/deregister and also a confirm button for delete 2024-01-29 19:38:27 +01:00
sara-pervana
b5008306cb added consume view and loaders on the buttons when clicked 2024-01-29 19:38:27 +01:00
sara-pervana
1b549549c0 added headers for axios requests 2024-01-29 19:38:27 +01:00
sara-pervana
29aa17ca7c added consume and register deregister as simple fetch 2024-01-29 19:38:27 +01:00
sara-pervana
e06afab048 minimum progress 2024-01-29 19:38:27 +01:00
sara-pervana
697d2685f3 small formatting fixes 2024-01-29 19:38:27 +01:00
Georg-Stahn
2f6ad476b3 Merge pull request 'added middleware for emulation' (#76) from georgdeamon into main
All checks were successful
checks-impure / test (push) Successful in 25s
checks / test (push) Successful in 1m24s
assets1 / test (push) Successful in 23s
Reviewed-on: #76
2024-01-28 19:37:23 +01:00
Georg-Stahn
dc04001dca emulate nix fmt changes
All checks were successful
checks-impure / test (pull_request) Successful in 26s
checks / test (pull_request) Successful in 3m35s
2024-01-28 18:26:00 +01:00
Georg-Stahn
3828402865 added middleware for emulation
Some checks failed
checks-impure / test (pull_request) Successful in 26s
checks / test (pull_request) Failing after 3m38s
2024-01-28 18:22:32 +01:00
Georg-Stahn
5b64eb4c3f Merge pull request 'georgdeamon' (#75) from georgdeamon into main
All checks were successful
checks-impure / test (push) Successful in 25s
checks / test (push) Successful in 1m12s
assets1 / test (push) Successful in 21s
Reviewed-on: #75
2024-01-26 11:47:50 +01:00
Georg-Stahn
eebc7eee20 filter out ap dlg in entity view
All checks were successful
checks-impure / test (pull_request) Successful in 26s
checks / test (pull_request) Successful in 3m19s
2024-01-26 09:18:04 +01:00
Georg-Stahn
2e787aa386 change deamon0 8000 to deamon1 8001 as emulation 2024-01-26 08:43:43 +01:00
Georg-Stahn
90c6df93f4 change deamon0 8000 to deamon1 8001 as emulation 2024-01-26 08:38:45 +01:00
374fdfdaea update ui-assets.nix
All checks were successful
checks-impure / test (push) Has been skipped
checks / test (push) Has been skipped
assets1 / test (push) Has been skipped
2024-01-26 00:28:17 +00:00
Sara Pervana
a240cfd340 Merge pull request 'Added a lot of fixes' (#73) from more-fixes into main
Some checks failed
checks-impure / test (push) Successful in 26s
checks / test (push) Successful in 1m14s
assets1 / test (push) Has been cancelled
Reviewed-on: #73
2024-01-26 01:25:58 +01:00
sara-pervana
eaec0feb96 warning fix
All checks were successful
checks-impure / test (pull_request) Successful in 26s
checks / test (pull_request) Successful in 3m3s
2024-01-26 01:19:32 +01:00
sara-pervana
f3ab6e1b45 removed warnings
Some checks failed
checks-impure / test (pull_request) Successful in 26s
checks / test (pull_request) Failing after 1m48s
2024-01-26 01:11:55 +01:00
sara-pervana
2c79dabce0 added a lot of stuff
Some checks failed
checks-impure / test (pull_request) Successful in 33s
checks / test (pull_request) Failing after 2m47s
2024-01-26 00:44:53 +01:00
sara-pervana
fd09d73edd small formatting fixes 2024-01-25 22:31:25 +01:00
sara-pervana
2000c1444a fix the issue of mermaid chart not rendering when changing routes 2024-01-25 21:56:18 +01:00
sara-pervana
b98c6090af remove fake data in the header of dlg and ap views 2024-01-25 20:51:45 +01:00
42a25a2eda update ui-assets.nix
All checks were successful
checks-impure / test (push) Has been skipped
checks / test (push) Has been skipped
assets1 / test (push) Has been skipped
2024-01-25 14:08:59 +00:00
b6b2bfbee5 Merge pull request 'Fixed sidebar and did problem' (#72) from Qubasa-main into main
Some checks failed
checks-impure / test (push) Successful in 26s
checks / test (push) Successful in 1m14s
assets1 / test (push) Has been cancelled
Reviewed-on: #72
2024-01-25 15:06:37 +01:00
09f80b1f42 Fixed sidebar and did problem
All checks were successful
checks-impure / test (pull_request) Successful in 26s
checks / test (pull_request) Successful in 1m13s
2024-01-25 14:47:52 +01:00
170ada9382 Fixed sidebar and did problem 2024-01-25 14:47:37 +01:00
a8b472e84d update ui-assets.nix
All checks were successful
checks-impure / test (push) Has been skipped
checks / test (push) Has been skipped
assets1 / test (push) Has been skipped
2024-01-25 13:39:23 +00:00
Erdem-Arslan
cf3fc347de Merge pull request 'dynamic-routing-sidebar' (#71) from dynamic-routing-sidebar into main
Some checks failed
checks-impure / test (push) Successful in 26s
checks / test (push) Successful in 1m12s
assets1 / test (push) Has been cancelled
Reviewed-on: #71
2024-01-25 14:37:03 +01:00
Arslan, Erdem
1d39ebcddc fix formatting
All checks were successful
checks-impure / test (pull_request) Successful in 29s
checks / test (pull_request) Successful in 3m47s
2024-01-25 14:32:11 +01:00
Arslan, Erdem
d978e413d5 filter out AP and DLG entities 2024-01-25 14:08:52 +01:00
8d72268922 update ui-assets.nix
All checks were successful
checks-impure / test (push) Has been skipped
checks / test (push) Has been skipped
assets1 / test (push) Has been skipped
2024-01-25 01:03:29 +00:00
Sara Pervana
840b3b6972 Merge pull request 'A lot of Diagram Fixes as well as other code changes' (#70) from diagram-fixes into main
Some checks failed
checks-impure / test (push) Successful in 25s
checks / test (push) Successful in 1m11s
assets1 / test (push) Has been cancelled
Reviewed-on: #70
2024-01-25 02:01:10 +01:00
sara-pervana
db1591a76e final final tailwind fixes 2024-01-25 02:01:10 +01:00
sara-pervana
1d6ea0ef0c afain fixed tailwind classes 2024-01-25 02:01:10 +01:00
sara-pervana
711ade4866 fixing some failuers on build 2024-01-25 02:01:10 +01:00
sara-pervana
7779441c87 final fixes for the diagram 2024-01-25 02:01:10 +01:00
sara-pervana
8e92415c34 small change to readme 2024-01-25 02:01:10 +01:00
sara-pervana
68e2f6d683 removed warnings 2024-01-25 02:01:10 +01:00
sara-pervana
c03da10e98 current progress with diagram and project fixes 2024-01-25 02:01:10 +01:00
60205b3c22 Merge pull request 'fix src_name and des_name being NULL' (#69) from Qubasa-main into main
All checks were successful
checks-impure / test (push) Successful in 28s
checks / test (push) Successful in 3m50s
assets1 / test (push) Successful in 22s
Reviewed-on: #69
2024-01-24 23:50:25 +01:00
da99e71b54 fix src_name and des_name being NULL 2024-01-24 23:50:25 +01:00
b978aabdd6 update ui-assets.nix
All checks were successful
checks-impure / test (push) Has been skipped
checks / test (push) Has been skipped
assets1 / test (push) Has been skipped
2024-01-24 22:42:32 +00:00
357568ebcb Merge pull request 'Fixed upload script' (#68) from Qubasa-main into main
Some checks failed
checks-impure / test (push) Successful in 26s
checks / test (push) Successful in 1m18s
assets1 / test (push) Has been cancelled
Reviewed-on: #68
2024-01-24 23:40:08 +01:00
ea2be8b7c7 nix fmt
All checks were successful
checks-impure / test (pull_request) Successful in 29s
checks / test (pull_request) Successful in 3m53s
2024-01-24 23:33:28 +01:00
672c8364bc Fixed upload script 2024-01-24 23:33:02 +01:00
2b8a9d9316 Merge pull request 'Fixed wrong ordering of eventmessages' (#66) from Qubasa-main into main
All checks were successful
checks-impure / test (push) Successful in 26s
checks / test (push) Successful in 1m12s
assets1 / test (push) Successful in 22s
Reviewed-on: #66
2024-01-24 22:47:06 +01:00
9b6fc699f2 Added push_docker script
All checks were successful
checks-impure / test (pull_request) Successful in 28s
checks / test (pull_request) Successful in 3m47s
2024-01-24 22:41:29 +01:00
d232510c0e Added build_docker.sh script 2024-01-24 22:41:29 +01:00
dcaecba393 Fixed incorrect grouping, in eventmessages 2024-01-24 22:41:03 +01:00
7901712c4c update ui-assets.nix
All checks were successful
checks-impure / test (push) Has been skipped
checks / test (push) Has been skipped
assets1 / test (push) Has been skipped
2024-01-24 21:26:10 +00:00
Erdem-Arslan
48df7352da Merge pull request 'dynamic-routing-sidebar' (#67) from dynamic-routing-sidebar into main
Some checks failed
checks-impure / test (push) Successful in 26s
checks / test (push) Successful in 1m14s
assets1 / test (push) Has been cancelled
Reviewed-on: #67
2024-01-24 22:23:50 +01:00
Arslan, Erdem
c5c4ab7178 remove variable index within mapping
All checks were successful
checks-impure / test (pull_request) Successful in 26s
checks / test (pull_request) Successful in 3m10s
2024-01-24 22:19:46 +01:00
Arslan, Erdem
22bcbf6819 fix formatting
Some checks failed
checks-impure / test (pull_request) Successful in 27s
checks / test (pull_request) Failing after 1m51s
2024-01-24 22:14:19 +01:00
Arslan, Erdem
2ab2282116 implement dynamic routing within the sidebar 2024-01-24 22:12:00 +01:00
1c6e33e74f Merge pull request 'Fixed wrong ordering of eventmessages' (#65) from Qubasa-main into main
All checks were successful
checks-impure / test (push) Successful in 26s
checks / test (push) Successful in 1m13s
assets1 / test (push) Successful in 22s
Reviewed-on: #65
2024-01-24 18:51:56 +01:00
1757bf1952 Fixed wrong ordering of eventmessages 2024-01-24 18:51:56 +01:00
Arslan, Erdem
ea0148fdaf formatting index.tsx 2024-01-21 21:56:03 +01:00
Arslan, Erdem
7ae1d5f768 create loading spinner for the consumer button 2024-01-21 21:34:53 +01:00
Arslan, Erdem
ec67dd1bac updating fetch request 2024-01-21 21:24:17 +01:00
Arslan, Erdem
522d7eb69a fetch the consume and register/deregister endpoints and add error handling 2024-01-21 21:15:51 +01:00
Arslan, Erdem
07a5a2fc24 Merge branch 'main' into register-deregister-actions
# Conflicts:
#	pkgs/ui/src/components/sequence_diagram/index.tsx
2024-01-21 20:45:24 +01:00
sara-pervana
59e33f3ead add register, deregister actions 2024-01-21 16:49:34 +01:00
42 changed files with 1558 additions and 592 deletions

2
.gitignore vendored
View File

@@ -23,3 +23,5 @@ htmlcov
# georgs # georgs
pkgs/.vs/ pkgs/.vs/
pkgs/clan-cli/.hypothesis/ pkgs/clan-cli/.hypothesis/
ui-assets.tar.gz
ui-release

12
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,12 @@
stages:
- release
release:
stage: release
script:
- set -x
- ./service-aware-network-front-end/pkgs/clan-cli/push_docker.sh
only:
- dev
tags:
- ea

View File

@@ -13,6 +13,20 @@ clan webui --reload --no-open --log-level debug --populate --emulate
- The `--emulate` flag will automatically run servers the database with dummy data for the fronted to communicate with (ap, dlg, c1 and c2) - The `--emulate` flag will automatically run servers the database with dummy data for the fronted to communicate with (ap, dlg, c1 and c2)
- To look into the emulated endpoints go to http://localhost:2979/emulate - To look into the emulated endpoints go to http://localhost:2979/emulate
# Using the Uploaded Docker Image
Pull the image
```bash
docker pull git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest
```
Run the image
```bash
docker run -p 127.0.0.1:2979:2979 git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest
```
# API Documentation # API Documentation
Api documentation can be found in the folder `pkgs/clan-cli/tests/openapi_client/docs/` Api documentation can be found in the folder `pkgs/clan-cli/tests/openapi_client/docs/`
@@ -24,12 +38,23 @@ For Entity object go to
- [tests/openapi_client/docs/ResolutionApi.md](tests/openapi_client/docs/ResolutionApi.md) - [tests/openapi_client/docs/ResolutionApi.md](tests/openapi_client/docs/ResolutionApi.md)
- [tests/openapi_client/docs/RepositoriesApi.md](tests/openapi_client/docs/RepositoriesApi.md) - [tests/openapi_client/docs/RepositoriesApi.md](tests/openapi_client/docs/RepositoriesApi.md)
# Building a Docker Image # Building a Docker Image if the Frontend Changed
To build a docker image of the frontend and backend be inside the `pkgs/clan-cli` folder and execute: To build a new docker image when the frontend code and/or backend code changed you first need
to get the `GITLAB_TOKEN` go to [repo access tokens](https://git.tu-berlin.de/internet-of-services-lab/service-aware-network-front-end/-/settings/access_tokens) and generate one.
- Make sure the Gitlab token has access to package registry.
Then execute
```bash ```bash
nix build .#clan-docker export GITLAB_TOKEN="<your-access-token>"
```
Afterwards you can execute:
```bash
./build_docker.sh
``` ```
This will create a symlink directory called `result` to a tar.gz docker file. Import it by executing: This will create a symlink directory called `result` to a tar.gz docker file. Import it by executing:
@@ -44,13 +69,60 @@ And then run the docker file by executing:
docker run -p 127.0.0.1:2979:2979 clan-docker:latest docker run -p 127.0.0.1:2979:2979 clan-docker:latest
``` ```
- To change parameters in the generated docker image edit the file : # Uploading a Docker Image
[flake-module.nix at line 22](flake-module.nix)
- Documentation on `dockerTools.buildImage` you can find here: https://nix.dev/tutorials/nixos/building-and-running-docker-images.html
## Docker build with UI changes You can use the script:
If changes to the UI have been made, and you want them to propagate to the docker container edit the file: [../ui/nix/ui-assets.nix](../ui/nix/ui-assets.nix). ```bash
./push_docker.sh
```
### The Script Explained
Login to the tu docker image server
```bash
docker login git.tu-berlin.de:5000
```
Tag the imported image
```bash
docker image tag clan-docker:latest git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest
```
Push the image to the git registry
```bash
docker image push git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest
```
# Upload UI assets as a package
To upload the release build UI assets to gitlab as a package
first get the `GITLAB_TOKEN`. Go to [repo access tokens](https://git.tu-berlin.de/internet-of-services-lab/service-aware-network-front-end/-/settings/access_tokens) and generate one.
- Make sure the Gitlab token has access to package registry.
To upload the UI assets as a package then execute:
```bash
./upload_ui_assets.sh
```
Please commit the changes to ui-assets.nix and push them to the repository.
If you want clan webui to use the new ui assets.
```bash
$ git commit -m "Update ui-assets.nix" "$PROJECT_DIR/pkgs/ui/nix/ui-assets.nix"
$ git push
```
If you execute `clan webui` the page you will see is a precompiled release version of the UI. This above script will update said precompiled release version. The `./build_docker.sh` script execute this to make sure that the included UI in the docker is up to date.
### The Script Explained
If changes to the UI have been made, and you want them to propagate to the docker container and the `clan webui` command edit the file: [../ui/nix/ui-assets.nix](../ui/nix/ui-assets.nix).
This is where a release version of the frontend is downloaded and integrated into the cli and the docker build. To do this first execute This is where a release version of the frontend is downloaded and integrated into the cli and the docker build. To do this first execute
```bash ```bash
@@ -60,14 +132,10 @@ nix build .#ui --out-link ui-release
Make a tarball out of it called `ui-assets.tar.gz` Make a tarball out of it called `ui-assets.tar.gz`
```bash ```bash
tar -czvf ui-assets.tar.gz ui-release/lib/node_modules/clan-ui/out/ tar --transform 's,^\.,assets,' -czvf "ui-assets.tar.gz" -C ui-release/result/lib/node_modules/*/out .
``` ```
Upload ui-assets.tar.gz to gitlab. To get the `GITLAB_TOKEN` go to [repo access tokens](https://git.tu-berlin.de/internet-of-services-lab/service-aware-network-front-end/-/settings/access_tokens) and generate one. Then execute Upload ui-assets.tar.gz to gitlab.
```bash
export GITLAB_TOKEN="<your-access-token>"
```
```bash ```bash
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \ curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
@@ -101,37 +169,30 @@ And now build the docker image:
nix build .#clan-docker nix build .#clan-docker
``` ```
# Uploading a Docker Image # Building a Docker Image if only the Backend Changed
Login to the tu docker image server To build a new docker image only when the backend code changed execute:
```bash ```bash
docker login git.tu-berlin.de:5000 nix build .#clan-docker
``` ```
Tag the imported image This is much faster then the `./build_docker.sh` script as it needs not to build the frontend and again.
This will create a symlink directory called `result` to a tar.gz docker file. Import it by executing:
```bash ```bash
docker image tag clan-docker:latest git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest docker load < result
``` ```
Push the image to the git registry And then run the docker file by executing:
```bash ```bash
docker image push git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest docker run -p 127.0.0.1:2979:2979 clan-docker:latest
``` ```
Pull the image - To change parameters in the generated docker image edit the file :
[flake-module.nix at line 22](flake-module.nix)
```bash - Documentation on `dockerTools.buildImage` you can find here: https://nix.dev/tutorials/nixos/building-and-running-docker-images.html
docker pull git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest
```
Run the image
```bash
docker run -p 127.0.0.1:2979:2979 git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest
```
# Auto Generating a Python Client # Auto Generating a Python Client

16
pkgs/clan-cli/build_docker.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
PROJECT_DIR=$(git rev-parse --show-toplevel)
"$PROJECT_DIR"/pkgs/clan-cli/upload_ui_assets.sh
nix build .#clan-docker
cat <<EOF
==============================
Please commit the changes to ui-assets.nix and push them to the repository.
If you want clan webui to use the new ui assets.
$ git commit -m "Update ui-assets.nix" "$PROJECT_DIR/pkgs/ui/nix/ui-assets.nix"
$ git push
EOF

View File

@@ -5,7 +5,11 @@ cors_url = [
"http://0.0.0.0", "http://0.0.0.0",
"http://[::]", "http://[::]",
] ]
cors_ports = ["*", 3000, 2979] cors_ports = ["*", 3000, 2979, 8001, 8002]
cors_whitelist = []
for u in cors_url:
for p in cors_ports:
cors_whitelist.append(f"{u}:{p}")
# host for the server, frontend, backend and emulators # host for the server, frontend, backend and emulators
host = "127.0.0.1" host = "127.0.0.1"

View File

@@ -6,6 +6,7 @@ from datetime import datetime
# Importing FastAPI and related components # Importing FastAPI and related components
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, JSONResponse from fastapi.responses import HTMLResponse, JSONResponse
# Importing configuration and schemas from the clan_cli package # Importing configuration and schemas from the clan_cli package
@@ -25,6 +26,14 @@ apps = [
(app_c1, config.c1_port), (app_c1, config.c1_port),
(app_c2, config.c2_port), (app_c2, config.c2_port),
] ]
for app, port in apps:
app.add_middleware(
CORSMiddleware,
allow_origins=config.cors_whitelist,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Healthcheck endpoints for different applications # Healthcheck endpoints for different applications
@@ -97,11 +106,13 @@ async def consume_service_from_other_entity_c1() -> HTMLResponse:
@app_c1.get("/v1/print_daemon1/register", response_class=JSONResponse) @app_c1.get("/v1/print_daemon1/register", response_class=JSONResponse)
async def register_c1() -> JSONResponse: async def register_c1() -> JSONResponse:
time.sleep(2)
return JSONResponse(content={"status": "registered"}, status_code=200) return JSONResponse(content={"status": "registered"}, status_code=200)
@app_c1.get("/v1/print_daemon1/deregister", response_class=JSONResponse) @app_c1.get("/v1/print_daemon1/deregister", response_class=JSONResponse)
async def deregister_c1() -> JSONResponse: async def deregister_c1() -> JSONResponse:
time.sleep(2)
return JSONResponse(content={"status": "deregistered"}, status_code=200) return JSONResponse(content={"status": "deregistered"}, status_code=200)
@@ -119,13 +130,15 @@ async def consume_service_from_other_entity_c2() -> HTMLResponse:
return HTMLResponse(content=html_content, status_code=200) return HTMLResponse(content=html_content, status_code=200)
@app_c2.get("/v1/print_daemon1/register", response_class=JSONResponse) @app_c2.get("/v1/print_daemon2/register", response_class=JSONResponse)
async def register_c2() -> JSONResponse: async def register_c2() -> JSONResponse:
time.sleep(2)
return JSONResponse(content={"status": "registered"}, status_code=200) return JSONResponse(content={"status": "registered"}, status_code=200)
@app_c2.get("/v1/print_daemon1/deregister", response_class=JSONResponse) @app_c2.get("/v1/print_daemon2/deregister", response_class=JSONResponse)
async def deregister_c2() -> JSONResponse: async def deregister_c2() -> JSONResponse:
time.sleep(2)
return JSONResponse(content={"status": "deregistered"}, status_code=200) return JSONResponse(content={"status": "deregistered"}, status_code=200)
@@ -138,32 +151,9 @@ async def ap_list_of_services() -> JSONResponse:
"uuid": "bdd640fb-0667-1ad1-1c80-317fa3b1799d", "uuid": "bdd640fb-0667-1ad1-1c80-317fa3b1799d",
"service_name": "Carlos Printing0", "service_name": "Carlos Printing0",
"service_type": "3D Printing", "service_type": "3D Printing",
"endpoint_url": "http://127.0.0.1:8000/v1/print_daemon0",
"other": {},
"entity_did": "did:sov:test:120",
"status": {"data": ["draft", "registered"]},
"action": {
"data": [
{
"name": "register",
"endpoint": "http://127.0.0.1:8000/v1/print_daemon0/register",
},
{
"name": "deregister",
"endpoint": "http://127.0.0.1:8000/v1/print_daemon0/deregister",
},
]
},
"usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}],
},
# Service 2 (similar structure)
{
"uuid": "23b8c1e9-3924-56de-3eb1-3b9046685257",
"service_name": "Carlos Printing1",
"service_type": "3D Printing",
"endpoint_url": "http://127.0.0.1:8001/v1/print_daemon1", "endpoint_url": "http://127.0.0.1:8001/v1/print_daemon1",
"other": {}, "other": {},
"entity_did": "did:sov:test:121", "entity_did": "did:sov:test:120",
"status": {"data": ["draft", "registered"]}, "status": {"data": ["draft", "registered"]},
"action": { "action": {
"data": [ "data": [
@@ -179,13 +169,14 @@ async def ap_list_of_services() -> JSONResponse:
}, },
"usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}], "usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}],
}, },
# Service 2 (similar structure)
{ {
"uuid": "bd9c66b3-ad3c-2d6d-1a3d-1fa7bc8960a9", "uuid": "23b8c1e9-3924-56de-3eb1-3b9046685257",
"service_name": "Carlos Printing2", "service_name": "Carlos Printing1",
"service_type": "3D Printing", "service_type": "3D Printing",
"endpoint_url": "http://127.0.0.1:8002/v1/print_daemon2", "endpoint_url": "http://127.0.0.1:8002/v1/print_daemon2",
"other": {}, "other": {},
"entity_did": "did:sov:test:122", "entity_did": "did:sov:test:121",
"status": {"data": ["draft", "registered"]}, "status": {"data": ["draft", "registered"]},
"action": { "action": {
"data": [ "data": [
@@ -202,12 +193,12 @@ async def ap_list_of_services() -> JSONResponse:
"usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}], "usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}],
}, },
{ {
"uuid": "972a8469-1641-9f82-8b9d-2434e465e150", "uuid": "bd9c66b3-ad3c-2d6d-1a3d-1fa7bc8960a9",
"service_name": "Carlos Printing3", "service_name": "Carlos Printing2",
"service_type": "3D Printing", "service_type": "3D Printing",
"endpoint_url": "http://127.0.0.1:8003/v1/print_daemon3", "endpoint_url": "http://127.0.0.1:8003/v1/print_daemon3",
"other": {}, "other": {},
"entity_did": "did:sov:test:123", "entity_did": "did:sov:test:122",
"status": {"data": ["draft", "registered"]}, "status": {"data": ["draft", "registered"]},
"action": { "action": {
"data": [ "data": [
@@ -223,6 +214,28 @@ async def ap_list_of_services() -> JSONResponse:
}, },
"usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}], "usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}],
}, },
{
"uuid": "972a8469-1641-9f82-8b9d-2434e465e150",
"service_name": "Carlos Printing3",
"service_type": "3D Printing",
"endpoint_url": "http://127.0.0.1:8004/v1/print_daemon4",
"other": {},
"entity_did": "did:sov:test:123",
"status": {"data": ["draft", "registered"]},
"action": {
"data": [
{
"name": "register",
"endpoint": "http://127.0.0.1:8004/v1/print_daemon4/register",
},
{
"name": "deregister",
"endpoint": "http://127.0.0.1:8004/v1/print_daemon4/deregister",
},
]
},
"usage": [{"times_consumed": 2, "consumer_entity_did": "did:sov:test:120"}],
},
] ]
return JSONResponse(content=res, status_code=200) return JSONResponse(content=res, status_code=200)

View File

@@ -1,11 +1,13 @@
import json
import logging import logging
import time import time
import typing import typing
from collections import OrderedDict
from typing import Any, List, Optional from typing import Any, List, Optional
import httpx import httpx
from fastapi import APIRouter, BackgroundTasks, Depends, Query from fastapi import APIRouter, BackgroundTasks, Depends, Query
from fastapi.responses import HTMLResponse, JSONResponse from fastapi.responses import HTMLResponse, PlainTextResponse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from clan_cli.config import ap_url, c1_url, c2_url, dlg_url, group_type_to_label from clan_cli.config import ap_url, c1_url, c2_url, dlg_url, group_type_to_label
@@ -166,6 +168,14 @@ def get_entity_by_roles(
return entity return entity
@router.get("/api/v1/entity_by_role", response_model=List[Entity], tags=[Tags.entities])
def get_entity_by_role(
role: Role, db: Session = Depends(sql_db.get_db)
) -> List[sql_models.Entity]:
entity = sql_crud.get_entity_by_role(db, roles=[role])
return entity
@router.get("/api/v1/entities", response_model=List[Entity], tags=[Tags.entities]) @router.get("/api/v1/entities", response_model=List[Entity], tags=[Tags.entities])
def get_all_entities( def get_all_entities(
skip: int = 0, limit: int = 100, db: Session = Depends(sql_db.get_db) skip: int = 0, limit: int = 100, db: Session = Depends(sql_db.get_db)
@@ -360,37 +370,54 @@ def create_eventmessage(
@typing.no_type_check @typing.no_type_check
@router.get( @router.get(
"/api/v1/event_messages", "/api/v1/event_messages",
response_class=JSONResponse, response_class=PlainTextResponse,
tags=[Tags.eventmessages], tags=[Tags.eventmessages],
) )
def get_all_eventmessages( def get_all_eventmessages(
skip: int = 0, limit: int = 100, db: Session = Depends(sql_db.get_db) skip: int = 0, limit: int = 100, db: Session = Depends(sql_db.get_db)
) -> JSONResponse: ) -> PlainTextResponse:
# SQL sorts eventmessages by timestamp, so we don't need to sort them here
eventmessages = sql_crud.get_eventmessages(db, skip=skip, limit=limit) eventmessages = sql_crud.get_eventmessages(db, skip=skip, limit=limit)
result: dict[int, dict[int, List[Eventmessage]]] = {} cresult: List[OrderedDict[int, OrderedDict[int, List[Eventmessage]]]] = []
for msg in eventmessages: cresult_idx = 0
cresult.append(OrderedDict())
for idx, msg in enumerate(eventmessages):
# Use the group_type_to_label from config.py to get the group name and msg_type name # Use the group_type_to_label from config.py to get the group name and msg_type name
group = group_type_to_label.get(msg.group, None) group = group_type_to_label.get(msg.group, None)
group_name = group.get("name", None) if group is not None else str(msg.group) group_name = (
str(group.get("name", None)) if group is not None else str(msg.group)
)
msg_type_name = ( msg_type_name = (
group.get(msg.msg_type, None) if group is not None else str(msg.msg_type) group.get(msg.msg_type, None) if group is not None else str(msg.msg_type)
) )
# Get the name of the src and des entity from the database # Get the name of the src and des entity from the database
src_name = sql_crud.get_entity_by_did(db, msg.src_did) src_name = sql_crud.get_entity_by_did(db, msg.src_did)
src_name = src_name if src_name is None else src_name.name src_name = msg.src_did if src_name is None else src_name.name
des_name = sql_crud.get_entity_by_did(db, msg.des_did) des_name = sql_crud.get_entity_by_did(db, msg.des_did)
des_name = des_name if des_name is None else des_name.name des_name = msg.des_did if des_name is None else des_name.name
# Initialize the result array and dictionary result = cresult[cresult_idx]
if result.get(group_name) is None:
result[group_name] = {} if result.get("group_name") is None:
if result[group_name].get(msg.group_id) is None: # Initialize the result array and dictionary
result[group_name][msg.group_id] = [] result["group_name"] = group_name
elif result["group_name"] != group_name:
# If the group name changed, create a new result array and dictionary
cresult_idx += 1
cresult.append(OrderedDict())
result = cresult[cresult_idx]
result["group_name"] = group_name
if result.get("groups") is None:
result["groups"] = OrderedDict()
if result["groups"].get(msg.group_id) is None:
result["groups"][msg.group_id] = []
# Append the eventmessage to the result array # Append the eventmessage to the result array
result_arr = result[group_name][msg.group_id] result_arr = result["groups"][msg.group_id]
result_arr.append( result_arr.append(
Eventmessage( Eventmessage(
id=msg.id, id=msg.id,
@@ -408,9 +435,7 @@ def get_all_eventmessages(
).dict() ).dict()
) )
# sort by timestamp return PlainTextResponse(content=json.dumps(cresult, indent=4), status_code=200)
result_arr.sort(key=lambda x: x["timestamp"])
return JSONResponse(content=result, status_code=200)
############################## ##############################

View File

@@ -1,7 +1,7 @@
# Imports # Imports
from typing import List, Optional from typing import List, Optional
from sqlalchemy import func from sqlalchemy import asc, func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy.sql.expression import true from sqlalchemy.sql.expression import true
@@ -319,4 +319,11 @@ def create_eventmessage(
def get_eventmessages( def get_eventmessages(
db: Session, skip: int = 0, limit: int = 100 db: Session, skip: int = 0, limit: int = 100
) -> List[sql_models.Eventmessage]: ) -> List[sql_models.Eventmessage]:
return db.query(sql_models.Eventmessage).offset(skip).limit(limit).all() # Use order_by and desc to sort by timestamp
return (
db.query(sql_models.Eventmessage)
.order_by(asc(sql_models.Eventmessage.timestamp))
.offset(skip)
.limit(limit)
.all()
)

View File

@@ -101,7 +101,7 @@ class Eventmessage(Base):
## Queryable body ## ## Queryable body ##
# Primary Key # Primary Key
id = Column(Integer, primary_key=True, autoincrement=True) id = Column(Integer, primary_key=True, autoincrement=True)
timestamp = Column(Integer, unique=True, index=True) timestamp = Column(Integer, index=True)
group = Column(Integer, index=True) group = Column(Integer, index=True)
group_id = Column(Integer, index=True) group_id = Column(Integer, index=True)
msg_type = Column(Integer, index=True) # message type for the label msg_type = Column(Integer, index=True) # message type for the label

9
pkgs/clan-cli/push_docker.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# shellcheck shell=bash
set -euo pipefail
docker login git.tu-berlin.de:5000
docker load < result
docker image tag clan-docker:latest git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest
docker image push git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest

View File

@@ -40,7 +40,7 @@ def test_health(api_client: ApiClient) -> None:
def create_entities(num: int = 5, role: str = "entity") -> list[EntityCreate]: def create_entities(num: int = 5, role: str = "entity") -> list[EntityCreate]:
res = [] res = []
for i in range(num): for i in range(1, num + 1):
en = EntityCreate( en = EntityCreate(
did=f"did:sov:test:12{i}", did=f"did:sov:test:12{i}",
name=f"C{i}", name=f"C{i}",
@@ -75,6 +75,7 @@ def create_entities(num: int = 5, role: str = "entity") -> list[EntityCreate]:
def create_service(idx: int, entity: Entity) -> ServiceCreate: def create_service(idx: int, entity: Entity) -> ServiceCreate:
idx += 1
se = ServiceCreate( se = ServiceCreate(
uuid=uuids[idx], uuid=uuids[idx],
service_name=f"Carlos Printing{idx}", service_name=f"Carlos Printing{idx}",
@@ -113,7 +114,7 @@ def test_create_entities(api_client: ApiClient) -> None:
def test_create_services(api_client: ApiClient) -> None: def test_create_services(api_client: ApiClient) -> None:
sapi = ServicesApi(api_client=api_client) sapi = ServicesApi(api_client=api_client)
eapi = EntitiesApi(api_client=api_client) eapi = EntitiesApi(api_client=api_client)
for midx, entity in enumerate(eapi.get_all_entities()): for midx, entity in enumerate(eapi.get_entity_by_roles([Role("service_prosumer")])):
service_obj = create_service(midx, entity) service_obj = create_service(midx, entity)
service = sapi.create_service(service_obj) service = sapi.create_service(service_obj)
assert service.uuid == service_obj.uuid assert service.uuid == service_obj.uuid
@@ -122,49 +123,49 @@ def test_create_services(api_client: ApiClient) -> None:
random.seed(77) random.seed(77)
def create_eventmessages(num: int = 2) -> list[EventmessageCreate]: def create_eventmessages(num: int = 4) -> list[EventmessageCreate]:
res = [] res = []
starttime = int(time.time()) starttime = int(time.time())
for i in range(num): for i2 in range(1, num + 1):
group_id = i % 5 + random.getrandbits(6) group_id = i2 % 5 + random.getrandbits(6) + 1
em_req_send = EventmessageCreate( em_req_send = EventmessageCreate(
timestamp=starttime + i * 10, timestamp=starttime + i2 * 10,
group=i % 5, group=i2 % 5,
group_id=group_id, group_id=group_id,
msg_type=1, msg_type=1,
src_did=f"did:sov:test:12{i}", src_did=f"did:sov:test:12{i2}",
des_did=f"did:sov:test:12{i+1}", des_did=f"did:sov:test:12{i2+1}",
msg={}, msg={},
) )
res.append(em_req_send) res.append(em_req_send)
em_req_rec = EventmessageCreate( em_req_rec = EventmessageCreate(
timestamp=starttime + (i * 10) + 2, timestamp=starttime + (i2 * 10) + 2,
group=i % 5, group=i2 % 5,
group_id=group_id, group_id=group_id,
msg_type=2, msg_type=2,
src_did=f"did:sov:test:12{i}", src_did=f"did:sov:test:12{i2}",
des_did=f"did:sov:test:12{i+1}", des_did=f"did:sov:test:12{i2+1}",
msg={}, msg={},
) )
res.append(em_req_rec) res.append(em_req_rec)
group_id = i % 5 + random.getrandbits(6) group_id = i2 % 5 + random.getrandbits(6)
em_res_send = EventmessageCreate( em_res_send = EventmessageCreate(
timestamp=starttime + i * 10 + 4, timestamp=starttime + i2 * 10 + 4,
group=i % 5, group=i2 % 5,
group_id=group_id, group_id=group_id,
msg_type=3, msg_type=3,
src_did=f"did:sov:test:12{i+1}", src_did=f"did:sov:test:12{i2+1}",
des_did=f"did:sov:test:12{i}", des_did=f"did:sov:test:12{i2}",
msg={}, msg={},
) )
res.append(em_res_send) res.append(em_res_send)
em_res_rec = EventmessageCreate( em_res_rec = EventmessageCreate(
timestamp=starttime + (i * 10) + 8, timestamp=starttime + (i2 * 10) + 8,
group=i % 5, group=i2 % 5,
group_id=group_id, group_id=group_id,
msg_type=4, msg_type=4,
src_did=f"did:sov:test:12{i+1}", src_did=f"did:sov:test:12{i2+1}",
des_did=f"did:sov:test:12{i}", des_did=f"did:sov:test:12{i2}",
msg={}, msg={},
) )
res.append(em_res_rec) res.append(em_res_rec)

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env bash
# shellcheck shell=bash
set -euo pipefail
# GITLAB_TOKEN
if [[ -z "${GITLAB_TOKEN:-}" ]]; then
cat <<EOF
GITLAB_TOKEN environment var is not set. Please generate a new token under
https://git.tu-berlin.de/internet-of-services-lab/service-aware-network-front-end/-/settings/access_tokens
EOF
exit 1
fi
tmpdir=$(mktemp -d)
cleanup() { rm -rf "$tmpdir"; }
trap cleanup EXIT
# Create a new ui build
nix build '.#ui' --out-link "$tmpdir/result"
tar --transform 's,^\.,assets,' -czvf "$tmpdir/assets.tar.gz" -C "$tmpdir"/result/lib/node_modules/*/out .
# upload ui assets to gitlab
gitlab_base="https://git.tu-berlin.de/api/v4/projects/internet-of-services-lab%2Fservice-aware-network-front-end"
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--upload-file "$tmpdir/assets.tar.gz" \
"$gitlab_base/packages/generic/ui-assets/1.0.0/ui-assets.tar.gz"
# write url and hash to ui-assets.nix
url="$gitlab_base/packages/generic/ui-assets/1.0.0/ui-assets.tar.gz"
PROJECT_DIR=$(git rev-parse --show-toplevel)
cat > "$PROJECT_DIR/pkgs/ui/nix/ui-assets.nix" <<EOF
{ fetchzip }:
fetchzip {
url = "$url";
sha256 = "$(nix-prefetch-url --unpack $url)";
}
EOF
cat <<EOF
Please commit the changes to ui-assets.nix and push them to the repository.
If you want clan webui to use the new ui assets.
$ git commit -m "Update ui-assets.nix" "$PROJECT_DIR/pkgs/ui/nix/ui-assets.nix"
$ git push
EOF

View File

@@ -1,5 +1,5 @@
{ fetchzip }: { fetchzip }:
fetchzip { fetchzip {
url = "https://gitea.gchq.icu/api/packages/IoSL/generic/IoSL-service-aware-frontend/12ndzp04vy7xmqk90gakb4igy2qjf1pcfmr94r2cmpjrkkljdgbi/assets.tar.gz"; url = "https://gitea.gchq.icu/api/packages/IoSL/generic/IoSL-service-aware-frontend/15svaig548jz1l8qsiqcycmw3hkb4805rb08mwlv2isxxshrj9ij/assets.tar.gz";
sha256 = "12ndzp04vy7xmqk90gakb4igy2qjf1pcfmr94r2cmpjrkkljdgbi"; sha256 = "15svaig548jz1l8qsiqcycmw3hkb4805rb08mwlv2isxxshrj9ij";
} }

View File

@@ -6,13 +6,15 @@ import { useGetAllRepositories } from "@/api/repositories/repositories";
import SummaryDetails from "@/components/summary_card"; import SummaryDetails from "@/components/summary_card";
import CustomTable from "@/components/table"; import CustomTable from "@/components/table";
import { import {
APSummaryDetails,
APAttachmentsTableConfig, APAttachmentsTableConfig,
APServiceRepositoryTableConfig, APServiceRepositoryTableConfig,
} from "@/config/access_point"; } from "@/config/access_point";
import { useEffect } from "react"; import { useEffect } from "react";
import useGetEntityByNameOrDid from "@/components/hooks/useGetEntityByNameOrDid";
import { projectConfig } from "@/config/config";
export default function AccessPoint() { export default function AccessPoint() {
const { entity } = useGetEntityByNameOrDid("AP");
const { const {
data: APAttachementData, data: APAttachementData,
isLoading: loadingAttachements, isLoading: loadingAttachements,
@@ -45,7 +47,7 @@ export default function AccessPoint() {
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
onRefresh(); onRefresh();
}, 5000); }, projectConfig.REFRESH_FREQUENCY);
return () => clearInterval(interval); return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -54,10 +56,25 @@ export default function AccessPoint() {
return ( return (
<div className="m-10"> <div className="m-10">
<SummaryDetails <SummaryDetails
fake
hasRefreshButton hasRefreshButton
onRefresh={onRefresh} onRefresh={onRefresh}
entity={{ name: "Access Point", details: APSummaryDetails }} entity={{
name: "Access Point",
details: [
{
label: "DID",
value: entity?.did,
},
{
label: "IP",
value: entity?.ip,
},
{
label: "Network",
value: entity?.network,
},
],
}}
/> />
<div> <div>
<h4>Attachment View</h4> <h4>Attachment View</h4>

View File

@@ -1,21 +0,0 @@
import Client from "@/app/client/client";
import { menuEntityEntries } from "@/components/sidebar";
export const dynamic = "error";
export const dynamicParams = false;
/*
The generateStaticParams function can be used in combination with dynamic route segments
to statically generate routes at build time instead of on-demand at request time.
During next dev, generateStaticParams will be called when you navigate to a route.
During next build, generateStaticParams runs before the corresponding Layouts or Pages are generated.
https://nextjs.org/docs/app/api-reference/functions/generate-static-params
*/
export function generateStaticParams() {
return menuEntityEntries.map((entry) => ({
name: entry.label,
}));
}
export default function Page({ params }: { params: { name: string } }) {
return <Client params={params} />;
}

View File

@@ -1,19 +1,14 @@
"use client"; "use client";
import { useEffect, useMemo, useRef, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { ClientTableConfig, ServiceTableConfig } from "@/config/client_1"; import { ClientTableConfig, ServiceTableConfig } from "@/config/client_1";
import CustomTable from "@/components/table"; import CustomTable from "@/components/table";
import { import {
Alert, Alert,
Button, Button,
Card,
CardContent,
CardHeader,
Snackbar, Snackbar,
Typography,
CircularProgress, CircularProgress,
IconButton, IconButton,
} from "@mui/material"; } from "@mui/material";
import CopyToClipboard from "@/components/copy_to_clipboard";
import { import {
attachEntity, attachEntity,
detachEntity, detachEntity,
@@ -26,6 +21,10 @@ import useGetEntityByNameOrDid from "@/components/hooks/useGetEntityByNameOrDid"
import { useGetAllServices } from "@/api/services/services"; import { useGetAllServices } from "@/api/services/services";
import axios from "axios"; import axios from "axios";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { useSearchParams } from "next/navigation";
import SummaryDetails from "@/components/summary_card";
import { projectConfig } from "@/config/config";
import ConsumeDisplayComponent from "@/components/consume_content";
interface SnackMessage { interface SnackMessage {
message: string; message: string;
@@ -105,8 +104,14 @@ const AttachButton = ({
); );
}; };
export default function Client({ params }: { params: { name: string } }) { export default function Client() {
const { name } = params; const searchParams = useSearchParams();
const name = searchParams.get("name") ?? "";
const [consumeContent, setConsumeContent] = useState(null);
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState<
SnackMessage | undefined
>(undefined);
const { entity: entity } = useGetEntityByNameOrDid(name); const { entity: entity } = useGetEntityByNameOrDid(name);
const { const {
@@ -133,24 +138,21 @@ export default function Client({ params }: { params: { name: string } }) {
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
onRefresh(); onRefresh();
}, 5000); }, projectConfig.REFRESH_FREQUENCY);
return () => clearInterval(interval); return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
const cardContentRef = useRef(null);
const [snackbarOpen, setSnackbarOpen] = useState(false);
const [snackbarMessage, setSnackbarMessage] = useState<
SnackMessage | undefined
>(undefined);
const closeSnackBar = () => { const closeSnackBar = () => {
setSnackbarMessage(undefined); setSnackbarMessage(undefined);
setSnackbarOpen(false); setSnackbarOpen(false);
}; };
console.log("entity", entity); // Consume
const handleConsumeContent = (content: any) => {
setConsumeContent(content);
};
if (services_loading) return <Skeleton height={500} />; if (services_loading) return <Skeleton height={500} />;
if (!services) return <Alert severity="error">Client not found</Alert>; if (!services) return <Alert severity="error">Client not found</Alert>;
@@ -178,34 +180,43 @@ export default function Client({ params }: { params: { name: string } }) {
</div> </div>
</div> </div>
<Card variant="outlined"> <SummaryDetails
<CardHeader entity={{
subheader="Summary" name: "",
action={<CopyToClipboard contentRef={cardContentRef} />} details: [
/> { label: "DID", value: entity?.did },
<CardContent ref={cardContentRef}> { label: "IP", value: entity?.ip },
<Typography color="text.primary" gutterBottom> { label: "Network", value: entity?.network },
DID: <code>{entity?.did}</code> ],
</Typography> }}
<Typography color="text.primary" gutterBottom> />
IP: <code>{entity?.ip}</code> <div
</Typography> style={{
<Typography color="text.primary" gutterBottom> display: "flex",
Network: <code>{entity?.network}</code> justifyContent: "space-between",
</Typography> flexWrap: "nowrap",
</CardContent> alignItems: "center",
</Card> }}
<div> >
<h4>Client View</h4> <div style={{ width: consumeContent ? "55%" : "100%" }}>
<CustomTable <h4>Service Consumer View</h4>
loading={services_loading} <CustomTable
data={clients} loading={services_loading}
configuration={ClientTableConfig} data={clients}
tkey="client-table" onConsumeAction={handleConsumeContent}
/> configuration={ClientTableConfig}
tkey="client-table"
/>
</div>
{consumeContent && (
<div style={{ width: "40%" }}>
<h4>Service Output</h4>
<ConsumeDisplayComponent htmlContent={consumeContent} />
</div>
)}
</div> </div>
<div> <div>
<h4>Service View</h4> <h4>Service Producer View</h4>
<CustomTable <CustomTable
loading={services_loading} loading={services_loading}
data={services?.data} data={services?.data}

View File

@@ -0,0 +1,5 @@
import Client from "@/app/client/client";
export default function Page() {
return <Client />;
}

View File

@@ -1,13 +1,16 @@
"use client"; "use client";
import { DLGResolutionTableConfig, DLGSummaryDetails } from "@/config/dlg"; import { DLGResolutionTableConfig } from "@/config/dlg";
import CustomTable from "@/components/table"; import CustomTable from "@/components/table";
import SummaryDetails from "@/components/summary_card"; import SummaryDetails from "@/components/summary_card";
import { useEffect } from "react"; import { useEffect } from "react";
import { useGetAllResolutions } from "@/api/resolution/resolution"; import { useGetAllResolutions } from "@/api/resolution/resolution";
import { mutate } from "swr"; import { mutate } from "swr";
import useGetEntityByNameOrDid from "@/components/hooks/useGetEntityByNameOrDid";
import { projectConfig } from "@/config/config";
export default function DLG() { export default function DLG() {
const { entity } = useGetEntityByNameOrDid("DLG");
const { const {
data: resolutionData, data: resolutionData,
isLoading: loadingResolutions, isLoading: loadingResolutions,
@@ -28,7 +31,7 @@ export default function DLG() {
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
onRefresh(); onRefresh();
}, 5000); }, projectConfig.REFRESH_FREQUENCY);
return () => clearInterval(interval); return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -37,12 +40,24 @@ export default function DLG() {
return ( return (
<div className="m-10"> <div className="m-10">
<SummaryDetails <SummaryDetails
fake
hasRefreshButton hasRefreshButton
onRefresh={onRefresh} onRefresh={onRefresh}
entity={{ entity={{
name: "Distributed Ledger Gateway", name: "Distributed Ledger Gateway",
details: DLGSummaryDetails, details: [
{
label: "DID",
value: entity?.did,
},
{
label: "IP",
value: entity?.ip,
},
{
label: "Network",
value: entity?.network,
},
],
}} }}
/> />
<div> <div>

View File

@@ -8,6 +8,7 @@ import dynamic from "next/dynamic";
import { useEffect } from "react"; import { useEffect } from "react";
import { mutate } from "swr"; import { mutate } from "swr";
import ErrorBoundary from "@/components/error_boundary"; import ErrorBoundary from "@/components/error_boundary";
import { projectConfig } from "@/config/config";
const NoSSRSequenceDiagram = dynamic( const NoSSRSequenceDiagram = dynamic(
() => import("../../components/sequence_diagram"), () => import("../../components/sequence_diagram"),
@@ -30,7 +31,7 @@ export default function Home() {
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
onRefresh(); onRefresh();
}, 5000); }, projectConfig.REFRESH_FREQUENCY);
return () => clearInterval(interval); return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -42,7 +43,6 @@ export default function Home() {
entity={{ name: "Home", details: [] }} entity={{ name: "Home", details: [] }}
hasRefreshButton={true} hasRefreshButton={true}
onRefresh={onRefresh} onRefresh={onRefresh}
hasAttachDetach={false}
/> />
<div> <div>

View File

@@ -51,7 +51,6 @@ export default function RootLayout({
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Service Aware Networks" /> <meta name="description" content="Service Aware Networks" />
<link rel="icon" href="tub-favicon.ico" sizes="any" /> <link rel="icon" href="tub-favicon.ico" sizes="any" />
{/* <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script> */}
<script <script
// eslint-disable-next-line react/no-danger // eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{

View File

@@ -0,0 +1,73 @@
import { Button, CircularProgress, Snackbar } from "@mui/material";
import { useState } from "react";
import axios from "axios";
const ConsumeAction = ({
endpoint,
onConsume,
}: {
endpoint: string;
rowData?: any;
onConsume?: any;
}) => {
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
if (error) console.error("Error in state", error);
const handleConsume = () => {
if (loading) return;
setLoading(true);
const axiosConfig = {
url: endpoint,
method: "GET",
data: null,
withCredentials: true,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
};
axios(axiosConfig)
.then((response) => {
if (onConsume) {
onConsume(response.data);
console.log("I got the data from consume: ", response.data);
}
})
.catch((error) => {
if (onConsume) onConsume(null);
console.error("Error happened during consume: ", error);
setError(error);
})
.finally(() => {
setLoading(false);
});
};
const handleCloseSnackbar = () => {
setError(null);
};
return (
<>
<Button disabled={loading} onClick={handleConsume} variant="contained">
{loading ? <CircularProgress size={24} /> : `Consume`}
</Button>
{error && (
<Snackbar
anchorOrigin={{ vertical: "top", horizontal: "center" }}
open={error}
autoHideDuration={2000}
message={`Something happened during consume: ${error}`}
onClose={handleCloseSnackbar}
/>
)}
</>
);
};
export default ConsumeAction;

View File

@@ -0,0 +1,9 @@
const ConsumeDisplayComponent = ({ htmlContent }: { htmlContent: any }) => {
return (
<div>
<div dangerouslySetInnerHTML={{ __html: htmlContent }} />
</div>
);
};
export default ConsumeDisplayComponent;

View File

@@ -1,26 +1,50 @@
import { useState } from "react"; import { useState, RefObject } from "react";
import { Button, Snackbar } from "@mui/material"; import { Tooltip, Snackbar } from "@mui/material";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
const CopyToClipboard = ({ contentRef }: { contentRef: any }) => { const CopyToClipboard = ({
contentRef,
textToCopy,
}: {
contentRef?: RefObject<HTMLDivElement>;
textToCopy?: string;
}) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const handleClick = () => { const handleClick = () => {
if (contentRef.current) { // Prioritize direct text copy if 'textToCopy' is provided
const text = contentRef.current.textContent; const text = textToCopy || contentRef?.current?.textContent || "";
navigator.clipboard.writeText(text); const copiedText = textToCopy ? JSON.stringify(text, null, 2) : text;
setOpen(true);
if (text) {
navigator.clipboard.writeText(copiedText).then(
() => {
setOpen(true);
},
(err) => {
console.error("Could not copy text: ", err);
},
);
} }
}; };
return ( return (
<> <>
<Button onClick={handleClick}>Copy</Button> <Tooltip placement="left" title="Copy to Clipboard">
<ContentCopyIcon onClick={handleClick} className="cursor-pointer" />
</Tooltip>
<Snackbar <Snackbar
open={open} open={open}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
autoHideDuration={2000} autoHideDuration={2000}
message="Copied to clipboard" message="Copied to clipboard!"
anchorOrigin={{
vertical: "bottom",
horizontal: "left",
}}
/> />
</> </>
); );
}; };
export default CopyToClipboard; export default CopyToClipboard;

View File

@@ -0,0 +1,73 @@
# CopyToClipboard Component
## Overview
The `CopyToClipboard` component is a versatile UI component designed to facilitate copying text to the user's clipboard. It can copy text from two sources: directly from a passed text string prop (`textToCopy`) or from the text content of a referenced div element (`contentRef`). The component includes a clickable icon and displays a confirmation snackbar notification once the copy action is successful.
## Props
The component accepts the following props:
1. `textToCopy` (optional): A string representing the direct text you want to copy. If provided, this text is copied to the clipboard when the icon is clicked.
2. `contentRef` (optional): A `RefObject<HTMLDivElement>` that references a div element. The text content of this div is copied to the clipboard if `textToCopy` is not provided.
## Behavior
- Copy Action: When the copy icon is clicked, the component:
- Prioritizes copying the text from the `textToCopy` prop if it's provided and not an empty string.
- If `textToCopy` is not provided or is empty, it then attempts to copy the text content of the element referenced by contentRef.
- Uses the Clipboard API (`navigator.clipboard.writeText`) to copy the text to the user's clipboard.
- Displays a snackbar notification confirming the copy action if successful.
- Snackbar Notification: A temporary notification that:
- Appears after the text is successfully copied.
- Displays the message "Copied to clipboard!".
- Disappears automatically after 2000 milliseconds and is positioned at the bottom left of the screen.
## How to Use
1. Import the `CopyToClipboard` component.
2. Use the component in one of the following ways:
3. Pass a `textToCopy` prop with the text you want to copy, OR
4. Pass a `contentRef` prop pointing to a div element containing the text you want to copy.
5. Render the `CopyToClipboard` component where you want the copy icon to appear.
## Example
Using `textToCopy` prop:
```javascript
import CopyToClipboard from "./CopyToClipboard";
const SomeComponent = () => {
return (
<div>
<CopyToClipboard textToCopy="Text to be copied" />
</div>
);
};
export default SomeComponent;
```
Using `contentRef` prop:
```javascript
import React, { useRef } from "react";
import CopyToClipboard from "./CopyToClipboard";
const SomeComponent = () => {
const textRef = useRef(null);
return (
<div>
<div ref={textRef}>Text to copy from ref</div>
<CopyToClipboard contentRef={textRef} />
</div>
);
};
export default SomeComponent;
```

View File

@@ -0,0 +1,231 @@
import { IEntityActions } from "@/types";
import {
Button,
Snackbar,
Alert,
AlertColor,
CircularProgress,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
} from "@mui/material";
import { useState } from "react";
import { deleteEntity } from "@/api/entities/entities";
import axios from "axios";
interface Props {
endpointData: IEntityActions[];
rowData?: any;
}
const SNACKBAR_DEFAULT = {
open: false,
message: "",
severity: "info" as AlertColor,
};
const EntityActions = ({ endpointData, rowData }: Props) => {
const [snackbar, setSnackbar] = useState<{
open: boolean;
message: string;
severity: AlertColor;
}>(SNACKBAR_DEFAULT);
const [registerData, setRegisterData] = useState(null);
const [registerError, setRegisterError] = useState(null);
const [loadingRegister, setLoadingRegister] = useState(false);
const [DeregisterData, setDeRegisterData] = useState(null);
const [DeregisterError, setDeRegisterError] = useState(null);
const [loadingDeRegister, setLoadingDeRegister] = useState(false);
const [loadingDelete, setLoadingDelete] = useState(false);
const [confirmDelete, setConfirmDelete] = useState(false);
if (registerData) console.log("Register Data in state", registerData);
if (registerError) console.error("Register Error in state", registerError);
if (DeregisterData) console.log("Register Data in state", DeregisterData);
if (DeregisterError)
console.error("Register Error in state", DeregisterError);
const onDeleteEntity = async () => {
setLoadingDelete(true);
if (rowData)
try {
const response = await deleteEntity({
entity_did: rowData?.entity_did,
});
setSnackbar({
open: true,
message: response.data.message,
severity: "success",
});
} catch (error) {
console.error("Error deleting entity: ", error);
setSnackbar({
open: true,
message: "Failed to delete entity.",
severity: "error",
});
} finally {
setLoadingDelete(false);
closeDeleteConfirmation();
}
};
const onRegisterEntity = (endpoint: string) => {
if (loadingRegister) return;
setLoadingRegister(true);
const axiosConfig = {
url: endpoint,
method: "GET",
data: null,
withCredentials: true,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
};
axios(axiosConfig)
.then((response) => {
setRegisterData(response.data);
console.log("I got the data from register: ", response.data);
setSnackbar({
open: true,
message: "Registered successfully!",
severity: "success",
});
})
.catch((error) => {
console.error("Error happened during register: ", error);
setRegisterError(error);
setSnackbar({
open: true,
message: error,
severity: "error",
});
})
.finally(() => {
setLoadingRegister(false);
});
};
const onDeregisterEntity = (endpoint: string) => {
if (loadingDeRegister) return;
setLoadingDeRegister(true);
const axiosConfig = {
url: endpoint,
method: "GET",
data: null,
};
axios(axiosConfig)
.then((response) => {
setDeRegisterData(response.data);
console.log("I got the data from deregister: ", response.data);
setSnackbar({
open: true,
message: "De-Registered successfully!",
severity: "success",
});
})
.catch((error) => {
console.error("Error happened during deregister: ", error);
setDeRegisterError(error);
setSnackbar({
open: true,
message: error,
severity: "error",
});
})
.finally(() => {
setLoadingDeRegister(false);
});
};
const handleCloseSnackbar = () => {
setSnackbar(SNACKBAR_DEFAULT);
};
const openDeleteConfirmation = () => {
setConfirmDelete(true);
};
const closeDeleteConfirmation = () => {
setConfirmDelete(false);
};
return (
<>
<div className="flex justify-between">
{endpointData.map(
({ name, endpoint }: IEntityActions, index: number) => {
const isRegister = name && name.toLocaleLowerCase() === "register";
// const isDeRegister = name && name.toLocaleLowerCase() === "deregister";
return (
<Button
disabled={loadingRegister || loadingDeRegister}
key={index}
onClick={() =>
isRegister
? onRegisterEntity(endpoint)
: onDeregisterEntity(endpoint)
}
variant="contained"
size="small"
>
{name}
</Button>
);
},
)}
<Button
disabled={loadingDelete}
onClick={openDeleteConfirmation}
size="small"
variant="contained"
>
Delete
</Button>
</div>
<Dialog open={confirmDelete} onClose={closeDeleteConfirmation}>
<DialogTitle>Delete Entity Confirmation</DialogTitle>
<DialogContent>
<DialogContentText>
Are you sure you want to delete this entity?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={closeDeleteConfirmation}>Cancel</Button>
<Button variant="contained" onClick={onDeleteEntity}>
{loadingDelete ? <CircularProgress size={24} /> : `Confirm`}
</Button>
</DialogActions>
</Dialog>
<Snackbar
anchorOrigin={{ vertical: "top", horizontal: "center" }}
open={snackbar.open}
autoHideDuration={5000}
onClose={handleCloseSnackbar}
>
<Alert
onClose={handleCloseSnackbar}
severity={snackbar?.severity}
sx={{ width: "100%" }}
>
{snackbar.message}
</Alert>
</Snackbar>
</>
);
};
export default EntityActions;

View File

@@ -0,0 +1,49 @@
import { useState, useEffect } from "react";
import axios from "axios";
import { projectConfig } from "@/config/config";
const useAxios = (
url: string,
method = "GET",
payload = null,
isFullUrl = false,
shouldFetch = false,
) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetch = () => {
setLoading(true);
setError(null);
const finalUrl = isFullUrl ? url : projectConfig.BASE_URL + url;
const axiosConfig = {
url: finalUrl,
method,
data: payload,
};
axios(axiosConfig)
.then((response) => {
setData(response.data);
})
.catch((error) => {
setError(error);
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
if (shouldFetch) {
fetch();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url, method, JSON.stringify(payload), shouldFetch]);
return { data, loading, error, refetch: fetch };
};
export default useAxios;

View File

@@ -1,33 +0,0 @@
import { useState, useEffect } from "react";
import axios from "axios";
import { BASE_URL } from "@/constants";
const useFetch = (url: string) => {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetch = () => {
setLoading(true);
axios
.get(BASE_URL + url)
.then((response) => {
setData(response.data);
})
.catch((error) => {
setError(error);
})
.finally(() => {
setLoading(false);
});
};
useEffect(() => {
fetch();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
return { data, loading, error, fetch };
};
export default useFetch;

View File

@@ -1,136 +1,93 @@
import { Eventmessage } from "@/api/model"; import { getGroupColor, sanitizeDID } from "@/utils/helpers";
export const generateMermaidString = (data: Eventmessage[] | undefined) => { export const generateMermaidString = (data: any) => {
if (!data || data.length === 0) return ""; if (!data || !data.length) return "";
const participants = Array.from(
new Set(data.flatMap((item) => [item.src_did, item.des_did])),
);
let mermaidString = "sequenceDiagram\n"; let mermaidString = "sequenceDiagram\n";
const participantDetails = new Map();
participants.forEach((participant, index) => { // Collect all unique participants along with their sanitized DIDs
mermaidString += ` participant ${String.fromCharCode( data.forEach((item: any) => {
65 + index, Object.values(item.groups).forEach((group: any) => {
)} as ${participant}\n`; group.forEach((msg: any) => {
// Apply sanitization to src_name and des_name if they are in DID format
const sanitizedSrcName = msg.src_name.includes(":")
? sanitizeDID(msg.src_name)
: msg.src_name;
const sanitizedDesName = msg.des_name.includes(":")
? sanitizeDID(msg.des_name)
: msg.des_name;
participantDetails.set(sanitizedSrcName, sanitizeDID(msg.src_did));
participantDetails.set(sanitizedDesName, sanitizeDID(msg.des_did));
});
});
}); });
let currentGroupId: number | null = null; // Add participants to the mermaid string with names and sanitized DIDs
participantDetails.forEach((sanitizedDID, name) => {
data.forEach((item, index) => { mermaidString += ` participant ${name} as ${name} <br/>${sanitizedDID}\n`;
const srcParticipant = String.fromCharCode(
65 + participants.indexOf(item.src_did),
);
const desParticipant = String.fromCharCode(
65 + participants.indexOf(item.des_did),
);
const timestamp = new Date(item.timestamp * 1000).toLocaleString();
const message = item.msg.text || `Event message ${index + 1}`;
if (item.group_id !== currentGroupId) {
if (currentGroupId !== null) {
mermaidString += ` end\n`;
}
mermaidString += ` alt Group ${item.group_id}\n`;
currentGroupId = item.group_id;
}
mermaidString += ` ${srcParticipant}->>${desParticipant}: [${timestamp}] ${message}\n`;
}); });
if (currentGroupId !== null) { // Iterate through each group
mermaidString += ` end\n`; data.forEach((item: any) => {
} let groupParticipants: any = new Set(); // This will collect participants for the current group
// Collect participants involved in each specific group
Object.values(item.groups).forEach((group: any) => {
group.forEach((msg: any) => {
const sanitizedSrcName = msg.src_name.includes(":")
? sanitizeDID(msg.src_name)
: msg.src_name;
const sanitizedDesName = msg.des_name.includes(":")
? sanitizeDID(msg.des_name)
: msg.des_name;
groupParticipants.add(sanitizedSrcName);
groupParticipants.add(sanitizedDesName);
});
});
// Convert the set of participants to a sorted array and then to a string
groupParticipants = Array.from(groupParticipants).sort().join(",");
// Get the group color from the config
const groupColor = getGroupColor(item.group_name);
// Add group note with only involved participants
mermaidString += `\n rect ${groupColor}\n Note over ${groupParticipants}: ${item.group_name}\n`;
Object.entries(item.groups).forEach(([groupId, messages]: any) => {
mermaidString += ` alt Group Id ${groupId}\n`;
messages.forEach((msg: any) => {
const sanitizedSrcName = msg.src_name.includes(":")
? sanitizeDID(msg.src_name)
: msg.src_name;
const sanitizedDesName = msg.des_name.includes(":")
? sanitizeDID(msg.des_name)
: msg.des_name;
const arrow = sanitizedSrcName > sanitizedDesName ? "-->>" : "->>";
mermaidString += ` ${sanitizedSrcName}${arrow}${sanitizedDesName}: [${msg.msg_type_name}]: Event Message ${msg.id}\n`;
});
mermaidString += " end\n";
});
mermaidString += " end\n";
});
return mermaidString; return mermaidString;
}; };
// Dummy Data export function extractAllEventMessages(data: any) {
const allMessagesArray: any = [];
export const dataFromBE = [ if (!data || data.length === 0) return allMessagesArray;
{ else
id: 12, data.forEach((groupData: any) => {
timestamp: 1704892813, Object.values(groupData.groups).forEach((messages: any) => {
group: 0, messages.forEach((message: any) => {
group_id: 12, allMessagesArray.push(message);
// "group_name": "Data", });
msg_type: 4, });
src_did: "did:sov:test:121", });
// "src_name": "Entity A", return allMessagesArray;
des_did: "did:sov:test:120", }
// "des_name": "Entity B",
msg: {
text: "Hello World",
},
},
{
id: 60,
timestamp: 1704892823,
group: 1,
group_id: 19,
msg_type: 4,
src_did: "did:sov:test:122",
des_did: "did:sov:test:121",
msg: {},
},
{
id: 30162,
timestamp: 1704892817,
group: 1,
group_id: 53,
msg_type: 2,
src_did: "did:sov:test:121",
des_did: "did:sov:test:122",
msg: {},
},
{
id: 63043,
timestamp: 1704892809,
group: 0,
group_id: 12,
msg_type: 3,
src_did: "did:sov:test:121",
des_did: "did:sov:test:120",
msg: {},
},
{
id: 66251,
timestamp: 1704892805,
group: 0,
group_id: 51,
msg_type: 1,
src_did: "did:sov:test:120",
des_did: "did:sov:test:121",
msg: {},
},
{
id: 85434,
timestamp: 1704892807,
group: 0,
group_id: 51,
msg_type: 2,
src_did: "did:sov:test:120",
des_did: "did:sov:test:121",
msg: {},
},
{
id: 124842,
timestamp: 1704892819,
group: 1,
group_id: 19,
msg_type: 3,
src_did: "did:sov:test:122",
des_did: "did:sov:test:121",
msg: {},
},
{
id: 246326,
timestamp: 1704892815,
group: 1,
group_id: 53,
msg_type: 1,
src_did: "did:sov:test:121",
des_did: "did:sov:test:122",
msg: {},
},
];

View File

@@ -2,48 +2,94 @@
import { useRef, useEffect, useState } from "react"; import { useRef, useEffect, useState } from "react";
import mermaid from "mermaid"; import mermaid from "mermaid";
import { IconButton } from "@mui/material"; import {
Button,
Card,
Chip,
Dialog,
DialogActions,
Tooltip,
DialogContent,
DialogContentText,
DialogTitle,
IconButton,
List,
TextField,
useMediaQuery,
} from "@mui/material";
//Icons
import RefreshIcon from "@mui/icons-material/Refresh"; import RefreshIcon from "@mui/icons-material/Refresh";
import ZoomInIcon from "@mui/icons-material/ZoomIn"; import ZoomInIcon from "@mui/icons-material/ZoomIn";
import ZoomOutIcon from "@mui/icons-material/ZoomOut"; import ZoomOutIcon from "@mui/icons-material/ZoomOut";
import FullscreenIcon from "@mui/icons-material/Fullscreen"; import FullscreenIcon from "@mui/icons-material/Fullscreen";
import DownloadIcon from "@mui/icons-material/Download"; import DownloadIcon from "@mui/icons-material/Download";
import ResetIcon from "@mui/icons-material/Autorenew"; import ResetIcon from "@mui/icons-material/Autorenew";
import Tooltip from "@mui/material/Tooltip"; import FilterAltIcon from "@mui/icons-material/FilterAlt";
// Custom Components
import { NoDataOverlay } from "../noDataOverlay"; import { NoDataOverlay } from "../noDataOverlay";
import { LoadingOverlay } from "../join/loadingOverlay";
import { useGetAllEventmessages } from "@/api/eventmessages/eventmessages"; import { useGetAllEventmessages } from "@/api/eventmessages/eventmessages";
import { mutate } from "swr"; import { mutate } from "swr";
import { LoadingOverlay } from "../join/loadingOverlay";
//import { generateMermaidString } from "./helpers"; import { extractAllEventMessages, generateMermaidString } from "./helpers";
import CopyToClipboard from "../copy_to_clipboard";
import { formatDateTime, getGroupById } from "@/utils/helpers";
const SequenceDiagram = () => { const SequenceDiagram = () => {
const { const {
// data: eventMessagesData, data: eventMessagesData,
isLoading: loadingEventMessages, isLoading: loadingEventMessages,
swrKey: eventMessagesKeyFunc, swrKey: eventMessagesKeyFunc,
} = useGetAllEventmessages(); } = useGetAllEventmessages();
const mermaidRef: any = useRef(null);
const [scale, setScale] = useState(1); const [scale, setScale] = useState(1);
const hasData = false; // TODO: Readd this, right now it's always false const [openFilters, setOpenFilters] = useState(false);
const [sequenceNr, setSequenceNr] = useState("");
const mermaidString = ""; //generateMermaidString(eventMessagesData?.data); const mermaidRef: any = useRef(null);
const hasData = eventMessagesData?.data && eventMessagesData?.data.length > 0;
const mermaidString = generateMermaidString(eventMessagesData?.data);
const allEventMessages = extractAllEventMessages(eventMessagesData?.data);
const dataDependency = JSON.stringify(hasData ? eventMessagesData?.data : "");
const userPrefersDarkmode = useMediaQuery("(prefers-color-scheme: dark)");
const iconButtonColor = userPrefersDarkmode ? "default" : "primary";
useEffect(() => { useEffect(() => {
if (!loadingEventMessages && hasData) const currentMermaidRef = mermaidRef?.current;
mermaid.initialize({
startOnLoad: false,
securityLevel: "loose",
sequence: {
mirrorActors: false,
},
});
if (mermaidRef.current) { if (!loadingEventMessages && hasData) {
mermaidRef.current.innerHTML = mermaidString; if (
mermaid.init(undefined, mermaidRef.current); currentMermaidRef &&
!currentMermaidRef.getAttribute("data-processed")
) {
mermaid.initialize({
startOnLoad: false,
securityLevel: "loose",
sequence: {
mirrorActors: true,
showSequenceNumbers: true,
},
});
}
if (currentMermaidRef) {
currentMermaidRef.innerHTML = mermaidString;
mermaid.init(undefined, currentMermaidRef);
}
} }
}, [loadingEventMessages, hasData, mermaidString]); return () => {
if (currentMermaidRef) {
currentMermaidRef.removeAttribute("data-processed");
currentMermaidRef.innerHTML = "";
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataDependency]);
useEffect(() => { useEffect(() => {
if (mermaidRef.current) { if (mermaidRef.current) {
@@ -126,55 +172,187 @@ const SequenceDiagram = () => {
} }
}; };
const toggleFilters = () => {
setOpenFilters((prevState) => !prevState);
};
const onSearchBySeqNumber = (e: any) => {
setSequenceNr(e.target.value);
};
const isFilterMatch = (index: number) => {
if (!sequenceNr) return true;
const filterSeqNrInt = parseInt(sequenceNr, 10);
return index + 1 === filterSeqNrInt;
};
if (loadingEventMessages) if (loadingEventMessages)
return <LoadingOverlay title="Loading Diagram" subtitle="Please wait..." />; return <LoadingOverlay title="Loading Diagram" subtitle="Please wait..." />;
return ( return (
<div className="flex flex-col items-end"> <>
{hasData ? ( <div className="flex flex-col items-end">
{hasData ? (
<>
<div className="flex justify-end">
<Tooltip placement="top" title="Filter Messages">
<IconButton color={iconButtonColor} onClick={toggleFilters}>
<FilterAltIcon />
</IconButton>
</Tooltip>
<Tooltip placement="top" title="Refresh Diagram">
<IconButton color={iconButtonColor} onClick={onRefresh}>
<RefreshIcon />
</IconButton>
</Tooltip>
<Tooltip title="Zoom In" placement="top">
<IconButton color={iconButtonColor} onClick={zoomIn}>
<ZoomInIcon />
</IconButton>
</Tooltip>
<Tooltip title="Zoom Out" placement="top">
<IconButton color={iconButtonColor} onClick={zoomOut}>
<ZoomOutIcon />
</IconButton>
</Tooltip>
<Tooltip title="Reset" placement="top">
<IconButton color={iconButtonColor} onClick={resetZoom}>
<ResetIcon />
</IconButton>
</Tooltip>
<Tooltip title="View in Fullscreen" placement="top">
<IconButton color={iconButtonColor} onClick={viewInFullScreen}>
<FullscreenIcon />
</IconButton>
</Tooltip>
<Tooltip title="Download as PNG" placement="top">
<IconButton color={iconButtonColor} onClick={downloadAsPng}>
<DownloadIcon />
</IconButton>
</Tooltip>
</div>
<div className="w-full p-2.5">
<div className="mermaid" ref={mermaidRef}></div>
</div>
</>
) : (
<div className="flex w-full justify-center">
<NoDataOverlay label="No Activity yet" />
</div>
)}
</div>
{openFilters && (
<> <>
<div className="flex justify-end"> <Dialog
<Tooltip placement="top" title="Refresh Diagram"> open={openFilters}
<IconButton color="default" onClick={onRefresh}> keepMounted
<RefreshIcon /> fullWidth
</IconButton> maxWidth="lg"
</Tooltip> onClose={toggleFilters}
<Tooltip title="Zoom In" placement="top"> >
<IconButton color="primary" onClick={zoomIn}> <DialogTitle>All Event Messages</DialogTitle>
<ZoomInIcon /> <DialogContent>
</IconButton> <DialogContentText>
</Tooltip> <div className="flex items-center gap-2.5">
<Tooltip title="Zoom Out" placement="top"> <label>Search by Sequence # </label>
<IconButton color="primary" onClick={zoomOut}> <TextField
<ZoomOutIcon /> onChange={onSearchBySeqNumber}
</IconButton> size="small"
</Tooltip> variant="outlined"
<Tooltip title="Reset" placement="top"> />
<IconButton color="primary" onClick={resetZoom}> </div>
<ResetIcon /> <List className="w-full" component="nav">
</IconButton> {allEventMessages
</Tooltip> .filter((_: any, index: number) => {
<Tooltip title="View in Fullscreen" placement="top"> return isFilterMatch(index);
<IconButton color="primary" onClick={viewInFullScreen}> })
<FullscreenIcon /> .map((message: any, index: number) => {
</IconButton> const {
</Tooltip> msg_type_name: msgType,
<Tooltip title="Download as PNG" placement="top"> des_name,
<IconButton color="primary" onClick={downloadAsPng}> src_name,
<DownloadIcon /> group,
</IconButton> group_id,
</Tooltip> timestamp,
</div> src_did,
<div className="w-full p-2.5"> des_did,
<div className="mermaid" ref={mermaidRef}></div> // msg, TODO: Need to use the content inside the msg to display in the diagram
</div> } = message;
const formattedTimeStamp = formatDateTime(timestamp);
const { groupIcon: IconComponent, groupName } =
getGroupById(group);
return (
<div
key={index}
style={{ marginBottom: 12 }}
className="flex items-center gap-5"
>
<Chip label={sequenceNr ? sequenceNr : ++index} />
<Card style={{ padding: 10 }} className="w-full">
<div
style={{ marginBottom: 12 }}
className="flex justify-between"
>
<div>
<span
style={{
marginBottom: 12,
fontWeight: "bold",
}}
className="flex items-center gap-2"
>
{IconComponent} {groupName}{" "}
<Chip label={msgType} />
</span>
<span>
Sender: {src_name} <Chip label={src_did} /> |{" "}
</span>
<span>
Receiver: {des_name} <Chip label={des_did} />{" "}
|{" "}
</span>
<span>Group: {group} | </span>
<span>Group ID: {group_id}</span>
</div>
<span>{formattedTimeStamp}</span>
</div>
<span className="font-bold">
Event Message {sequenceNr ? sequenceNr : index++}
</span>
<div
className="mt-4 flex"
style={{
border: "1px solid #f1f1f1",
borderRadius: 5,
}}
>
<pre className="flex-1 p-2">
{JSON.stringify(message, null, 2)}
</pre>
<div className="shrink-0 p-2">
<CopyToClipboard textToCopy={message} />
</div>
</div>
</Card>
</div>
);
})}
</List>
</DialogContentText>
</DialogContent>
<DialogActions className="p-4">
<Button variant="contained" onClick={toggleFilters}>
Close
</Button>
</DialogActions>
</Dialog>
</> </>
) : (
<div className="flex w-full justify-center">
<NoDataOverlay label="No Activity yet" />
</div>
)} )}
</div> </>
); );
}; };

View File

@@ -9,6 +9,8 @@ import {
Tooltip, Tooltip,
useMediaQuery, useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { useGetEntityByRole } from "@/api/entities/entities";
import { Role } from "@/api/model/role";
import Image from "next/image"; import Image from "next/image";
import React, { ReactNode } from "react"; import React, { ReactNode } from "react";
@@ -33,33 +35,6 @@ type MenuEntry = {
subMenuEntries?: MenuEntry[]; subMenuEntries?: MenuEntry[];
}; };
export const menuEntityEntries: MenuEntry[] = [
{
icon: <PersonIcon />,
label: "C1",
to: "/client/C1",
disabled: false,
},
{
icon: <PersonIcon />,
label: "C2",
to: "/client/C2",
disabled: false,
},
{
icon: <PersonIcon />,
label: "C3",
to: "/client/C3",
disabled: false,
},
{
icon: <PersonIcon />,
label: "C4",
to: "/client/C4",
disabled: false,
},
];
export const menuEntries: MenuEntry[] = [ export const menuEntries: MenuEntry[] = [
{ {
icon: <HomeIcon />, icon: <HomeIcon />,
@@ -96,6 +71,9 @@ interface SidebarProps {
} }
export function Sidebar(props: SidebarProps) { export function Sidebar(props: SidebarProps) {
const { data: entityData } = useGetEntityByRole({
role: Role.service_prosumer,
});
const { show, onClose } = props; const { show, onClose } = props;
const [activeMenuItem, setActiveMenuItem] = React.useState( const [activeMenuItem, setActiveMenuItem] = React.useState(
typeof window !== "undefined" ? window.location.pathname : "", typeof window !== "undefined" ? window.location.pathname : "",
@@ -112,13 +90,28 @@ export function Sidebar(props: SidebarProps) {
setCollapseMenuOpen(!collapseMenuOpen); setCollapseMenuOpen(!collapseMenuOpen);
}; };
const menuEntityEntries: MenuEntry[] = React.useMemo(() => {
if (entityData) {
return Array.isArray(entityData.data)
? entityData.data.map((entity: any) => ({
icon: <PersonIcon />,
label: entity.name,
to: entity.name,
disabled: false,
}))
: [];
} else {
return [];
}
}, [entityData]);
React.useEffect(() => { React.useEffect(() => {
if (isSmallerScreen) { if (isSmallerScreen) {
setCollapseMenuOpen(false); setCollapseMenuOpen(false);
} else { } else {
setCollapseMenuOpen(true); setCollapseMenuOpen(true);
} }
}, [isSmallerScreen]); }, [isSmallerScreen, entityData]);
return ( return (
<aside <aside
@@ -213,31 +206,36 @@ export function Sidebar(props: SidebarProps) {
unmountOnExit unmountOnExit
> >
<List component="div" disablePadding> <List component="div" disablePadding>
{menuEntityEntries.map((menuEntry, idx) => ( {menuEntityEntries?.map((menuEntry, idx) => (
<ListItemButton <Link
key={idx} key={"entity-link-" + idx}
sx={{ pl: 4 }} href={`/client?name=${menuEntry.to}`}
className="lg:justify-normal" style={{ textDecoration: "none", color: "white" }}
LinkComponent={Link}
href={menuEntry.to}
disabled={menuEntry.disabled}
selected={activeMenuItem === menuEntry.to}
onClick={() => handleMenuItemClick(menuEntry.to)}
> >
<ListItemIcon <ListItemButton
color="inherit" key={idx}
className="overflow-hidden text-white lg:justify-normal" sx={{ pl: 4 }}
className="lg:justify-normal"
LinkComponent={Link}
disabled={menuEntry.disabled}
selected={activeMenuItem === menuEntry.to}
onClick={() => handleMenuItemClick(menuEntry.to)}
> >
{menuEntry.icon} <ListItemIcon
</ListItemIcon> color="inherit"
<ListItemText className="overflow-hidden text-white lg:justify-normal"
primary={menuEntry.label} >
primaryTypographyProps={{ {menuEntry.icon}
color: "inherit", </ListItemIcon>
}} <ListItemText
className="hidden lg:block" primary={menuEntry.label}
/> primaryTypographyProps={{
</ListItemButton> color: "inherit",
}}
className="hidden lg:block"
/>
</ListItemButton>
</Link>
))} ))}
</List> </List>
</Collapse> </Collapse>

View File

@@ -13,29 +13,17 @@ import { EntityDetails, ISummaryDetails } from "@/types";
const SummaryDetails = ({ const SummaryDetails = ({
entity, entity,
hasRefreshButton, hasRefreshButton,
hasAttachDetach,
fake, fake,
onRefresh, onRefresh,
}: ISummaryDetails) => { }: ISummaryDetails) => {
const cardContentRef = useRef(null); const cardContentRef = useRef<HTMLDivElement>(null);
const hasDetails = entity.details && entity.details.length > 0; const hasDetails = entity.details && entity.details.length > 0;
return ( return (
<> <>
<div <div className="flex items-center justify-between">
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}}
>
<h2>{entity.name}</h2> <h2>{entity.name}</h2>
<div> <div>
{hasAttachDetach && (
<Button className="mr-6" variant="contained">
Attach / Detach
</Button>
)}
{hasRefreshButton && ( {hasRefreshButton && (
<Button onClick={onRefresh} variant="contained"> <Button onClick={onRefresh} variant="contained">
Refresh Refresh
@@ -46,7 +34,7 @@ const SummaryDetails = ({
{hasDetails && ( {hasDetails && (
<Card variant="outlined"> <Card variant="outlined">
<CardHeader <CardHeader
subheader={fake ? "Summary (Fake Data)" : "Summary"} subheader={`Summary ${fake ? "(Fake Data)" : ""}`}
action={<CopyToClipboard contentRef={cardContentRef} />} action={<CopyToClipboard contentRef={cardContentRef} />}
/> />
<CardContent ref={cardContentRef}> <CardContent ref={cardContentRef}>

View File

@@ -0,0 +1,52 @@
# SummaryDetails Component
## Overview
The `SummaryDetails` component is a flexible UI component designed to display a summary of details related to a specific entity in a card format. It is equipped with optional functionalities such as refreshing the data.
## Props
The component accepts the following props:
1. `entity`: An object representing the entity whose details are to be displayed. It should have a name and details, where details is an array of `EntityDetails` objects.
2. `hasRefreshButton` (optional): A boolean indicating if a Refresh button should be displayed. If true, the button is shown, allowing the user to refresh the entity details.
3. `fake` (optional): A boolean indicating if the displayed data is fake. If true, a label '(Fake Data)' is displayed in the card's header.
4. `onRefresh` (optional): A function to be called when the Refresh button is clicked. It should handle the logic for refreshing the entity details.
## UI Structure
- The component starts with a flex container displaying the entity's name and optional button (Refresh) based on the props.
- If the entity has details (checked by `hasDetails`), it displays a card containing:
- A `CardHeader` with a subheader indicating it's a summary and whether the data is fake.
- A `CopyToClipboard` component attached to the card's action, allowing the user to copy the details.
- A `CardContent` section listing all the details. Each detail is displayed as a `Typography` component, showing the label and value of each `EntityDetails` item.
## How to Use
1. Import the `SummaryDetails` component.
2. Create an entity object with a name and details, where details is an array of objects with label and value.
3. Optionally, decide if you want the Refresh functionality by setting `hasRefreshButton` to `true`.
4. If using the `Refresh` functionality, provide an `onRefresh` function to handle the logic.
5. Render the `SummaryDetails` component with the desired props.
## Example
```javascript
<SummaryDetails
entity={{
name: "Sample Entity",
details: [
{ label: "Detail 1", value: "Value 1" },
{ label: "Detail 2", value: "Value 2" },
// ... more details
],
}}
hasRefreshButton={true}
onRefresh={() => {
// handle refresh button logic/callback
}}
/>
```

View File

@@ -12,7 +12,13 @@ import { ICustomTable, CustomTableConfiguration } from "@/types";
import { Checkbox, Skeleton } from "@mui/material"; import { Checkbox, Skeleton } from "@mui/material";
import ErrorBoundary from "@/components/error_boundary"; import ErrorBoundary from "@/components/error_boundary";
const CustomTable = ({ configuration, data, loading, tkey }: ICustomTable) => { const CustomTable = ({
configuration,
data,
loading,
tkey,
onConsumeAction,
}: ICustomTable) => {
if (loading) if (loading)
return <Skeleton variant="rectangular" animation="wave" height={200} />; return <Skeleton variant="rectangular" animation="wave" height={200} />;
@@ -23,7 +29,12 @@ const CustomTable = ({ configuration, data, loading, tkey }: ICustomTable) => {
const renderTableCell = ( const renderTableCell = (
value: any, value: any,
cellKey: string, cellKey: string,
render?: (param: any) => void | undefined, render?: (
param: any,
data?: any,
onFunc?: (param: any) => void,
) => void | undefined,
rowData?: any,
) => { ) => {
let renderedValue = value; let renderedValue = value;
@@ -35,10 +46,17 @@ const CustomTable = ({ configuration, data, loading, tkey }: ICustomTable) => {
renderedValue = <Checkbox disabled checked={value} />; renderedValue = <Checkbox disabled checked={value} />;
// cover use case if we want to render a component // cover use case if we want to render a component
if (render) renderedValue = render(value); if (render) renderedValue = render(value, rowData, onConsumeAction);
if (typeof renderedValue === "object" && render === undefined) {
// catch use case where the value is an object but the render function is not provided in the table config
if (
typeof value === "object" &&
!Array.isArray(value) &&
render === undefined
) {
console.warn("Missing render function for column " + cellKey); console.warn("Missing render function for column " + cellKey);
} }
return ( return (
<ErrorBoundary> <ErrorBoundary>
<StyledTableCell key={cellKey} align="left"> <StyledTableCell key={cellKey} align="left">
@@ -59,17 +77,18 @@ const CustomTable = ({ configuration, data, loading, tkey }: ICustomTable) => {
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{data.map((data: any, rowIndex: number) => ( {data.map((rowData: any, rowIndex: number) => (
<StyledTableRow key={rowIndex}> <StyledTableRow key={rowIndex}>
{configuration.map( {configuration.map(
(column: CustomTableConfiguration, columnIndex: number) => { (column: CustomTableConfiguration, columnIndex: number) => {
const cellValue: any = data[column.key]; const cellValue: any = rowData[column.key];
const cellKey = tkey + ":" + column.key + ":" + rowIndex; const cellKey = tkey + ":" + column.key + ":" + rowIndex;
const renderComponent = column?.render; const renderComponent = column?.render;
return renderTableCell( return renderTableCell(
cellValue, cellValue,
cellKey + ":" + columnIndex, cellKey + ":" + columnIndex,
renderComponent, renderComponent,
rowData,
); );
}, },
)} )}

View File

@@ -0,0 +1,75 @@
# CustomTable Component
## Overview
The `CustomTable` component is a dynamic and flexible table designed to display data in a structured tabular format. It is highly customizable, allowing specific rendering for different data types and providing a user-friendly display for loading and empty data states.
## Props
The component accepts the following props:
1. `configuration`: An array of `CustomTableConfiguration` objects defining the structure and customization options for table columns, including:
- `key`: Corresponds to the key in the data objects for the column.
- `label`: Text label for the column header.
- `render` (optional): A function for custom rendering of the cell's content.
- `data`: An array of data objects, each representing a row in the table.
- `loading` (optional): If `true`, displays a loading state (skeleton screen).
- `tkey`: A unique key for the table, used for constructing unique cell keys.
## Behavior
- **Loading State**: Displays a `Skeleton` loader when `loading` is `true`.
- **Empty Data State**: Displays a `NoDataOverlay` component with a message if no data is available.
- **Data Rendering**:
- Dynamically renders cells based on `configuration`.
- Handles different data types:
- Joins array elements with a comma.
- Shows a disabled checkbox for boolean values.
- Uses the provided `render` function for custom rendering.
- Logs a warning if a cell's value is an object (not an array), and no `render` function is provided.
- **Error Handling**: Each cell is wrapped in an `ErrorBoundary` component for graceful error handling.
## How to Use
1. Import the `CustomTable` component.
2. Define the `configuration` for table columns.
3. Provide `data` as an array of objects corresponding to the configuration.
4. Optionally, control the loading state with the `loading` prop.
5. Provide a unique `tkey` for the table.
## Example
```javascript
import CustomTable from "./CustomTable";
const tableConfig = [
{ key: "name", label: "Name" },
{ key: "age", label: "Age" },
{
key: "isActive",
label: "Active",
render: (isActive) => (isActive ? "Yes" : "No"),
},
];
const tableData = [
{ name: "John Doe", age: 30, isActive: true },
{ name: "Jane Smith", age: 25, isActive: false },
];
const SomeComponent = () => {
return (
<div>
<CustomTable
configuration={tableConfig}
data={tableData}
loading={false}
tkey="unique-table-key"
/>
</div>
);
};
export default SomeComponent;
```

View File

@@ -4,7 +4,6 @@ import TableRow from "@mui/material/TableRow";
export const StyledTableCell = styled(TableCell)(({ theme }) => ({ export const StyledTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: { [`&.${tableCellClasses.head}`]: {
// backgroundColor: theme.palette.common.black,
backgroundColor: "#003258", backgroundColor: "#003258",
color: theme.palette.common.white, color: theme.palette.common.white,
}, },

View File

@@ -1,19 +1,4 @@
// AP - Summary // AP - 2 Tables Configurations to display labels
export const APSummaryDetails = [
{
label: "DID",
value: "did:sov:test:1274",
},
{
label: "IP",
value: "127.0.0.2",
},
{
label: "Network",
value: "Carlo's Home Network",
},
];
export const APAttachmentsTableConfig = [ export const APAttachmentsTableConfig = [
{ {
@@ -61,29 +46,9 @@ export const APServiceRepositoryTableConfig = [
label: "Status", label: "Status",
render: (value: any) => { render: (value: any) => {
let renderedValue: any = ""; let renderedValue: any = "";
if (Array.isArray(value.data)) { if (Array.isArray(value.data)) renderedValue = value.data.join(", ");
renderedValue = value.data.join(", "); else console.error("Status is not an array", value);
} else {
console.error("Status is not an array", value);
}
return renderedValue; return renderedValue;
}, },
}, },
// {
// key: "other",
// label: "Type",
// render: (value: any) => {
// let renderedValue: any = "";
// if (typeof value === "object") {
// const label = Object.keys(value)[0];
// const info = value[label];
// renderedValue = (
// <code>
// {label} {info}
// </code>
// );
// }
// return renderedValue;
// },
// },
]; ];

View File

@@ -1,7 +1,5 @@
import { Button, IconButton, Tooltip } from "@mui/material"; import EntityActions from "@/components/entity_actions";
import AddCircleIcon from "@mui/icons-material/AddCircle"; import ConsumeAction from "@/components/consume_action";
import RemoveCircleIcon from "@mui/icons-material/RemoveCircle";
import DeleteIcon from "@mui/icons-material/Delete";
export const ClientTableConfig = [ export const ClientTableConfig = [
{ {
@@ -15,11 +13,13 @@ export const ClientTableConfig = [
{ {
key: "endpoint_url", key: "endpoint_url",
label: "End Point", label: "End Point",
render: () => { render: (value: any, rowData: any, onConsume: any) => {
return ( return (
<Button disabled variant="outlined"> <ConsumeAction
Consume rowData={rowData}
</Button> onConsume={onConsume}
endpoint={value}
/>
); );
}, },
}, },
@@ -88,39 +88,10 @@ export const ServiceTableConfig = [
{ {
key: "action", key: "action",
label: "Actions", label: "Actions",
render: () => { render: (value: any, rowData?: any) => {
return ( if (value && value?.data.length > 0)
<> return <EntityActions rowData={rowData} endpointData={value.data} />;
<Tooltip title="Register" placement="top"> else return "N/A";
<IconButton disabled size="small">
<AddCircleIcon />
</IconButton>
</Tooltip>
<Tooltip title="De-register" placement="top">
<IconButton disabled size="small">
<RemoveCircleIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete" placement="top">
<IconButton disabled size="small" color="secondary">
<DeleteIcon />
</IconButton>
</Tooltip>
</>
);
// let renderedValue: any = "";
// if (typeof value === "object")
// renderedValue = (
// <>
// {[...value.data, { name: 'Delete', endpoint: '' }].map((actionType: any) => (
// <>
// <Button disabled style={{ marginRight: 8 }} variant="outlined" size="small">{actionType.name}</Button>
// </>
// ))}
// </>
// );
// return renderedValue;
}, },
}, },
]; ];

View File

@@ -0,0 +1,112 @@
import AttachmentIcon from "@mui/icons-material/Attachment";
import ArticleIcon from "@mui/icons-material/Article";
import ConstructionIcon from "@mui/icons-material/Construction";
import AssignmentTurnedInIcon from "@mui/icons-material/AssignmentTurnedIn";
import RemoveCircleIcon from "@mui/icons-material/RemoveCircle";
import AddCircleIcon from "@mui/icons-material/AddCircle";
import PageviewIcon from "@mui/icons-material/Pageview";
import BuildIcon from "@mui/icons-material/Build";
export const projectConfig: any = {
BASE_URL: "http://localhost:2979/api/v1",
REFRESH_FREQUENCY: 5000,
GROUPS: [
{
groupName: "Attachement",
groupId: 1,
groupColor: "rgb(230, 230, 250)",
groupIcon: <AttachmentIcon />,
messageTypes: [
{ id: 1, label: "Attachment Request Send" },
{ id: 2, label: "Attachment Request Received" },
{ id: 3, label: "Attachment Response Send" },
{ id: 4, label: "Attachment Response Received" },
],
},
{
groupName: "Connection Setup",
groupId: 2,
groupColor: "rgb(245, 222, 179)",
groupIcon: <ConstructionIcon />,
messageTypes: [
{ id: 1, label: "Connection request send" },
{ id: 2, label: "Connection request received" },
{ id: 3, label: "Connection response send" },
{ id: 4, label: "Connection response received" },
],
},
{
groupName: "Presentation",
groupId: 3,
groupColor: "rgb(255, 209, 220)",
groupIcon: <ArticleIcon />,
messageTypes: [
{ id: 1, label: "Request send" },
{ id: 2, label: "Request received" },
{ id: 3, label: "Presentation send" },
{ id: 4, label: "Presentation received" },
{ id: 5, label: "Presentation acknowledged" },
],
},
{
groupName: "DID Resolution",
groupId: 4,
groupColor: "rgb(189, 255, 243)",
groupIcon: <AssignmentTurnedInIcon />,
messageTypes: [
{ id: 1, label: "DID Resolution Request send" },
{ id: 2, label: "DID Resolution Request received" },
{ id: 3, label: "DID Resolution Response send" },
{ id: 4, label: "DID Resolution Response received" },
],
},
{
groupName: "Service De-registration",
groupId: 5,
groupColor: "rgb(255, 218, 185)",
groupIcon: <RemoveCircleIcon />,
messageTypes: [
{ id: 1, label: "Service De-registration send" },
{ id: 2, label: "Service De-registration received" },
{ id: 3, label: "Service De-registration successful send" },
{ id: 4, label: "Service De-registration successful received" },
],
},
{
groupName: "Service Registration",
groupId: 6,
groupColor: "rgb(200, 162, 200)",
groupIcon: <AddCircleIcon />,
messageTypes: [
{ id: 1, label: "Service Registration send" },
{ id: 2, label: "Service Registration received" },
{ id: 3, label: "Service Registration successful send" },
{ id: 4, label: "Service Registration successful received" },
],
},
{
groupName: "Service Discovery",
groupId: 7,
groupColor: "rgb(255, 250, 205)",
groupIcon: <PageviewIcon />,
messageTypes: [
{ id: 1, label: "Service Discovery send" },
{ id: 2, label: "Service Discovery received" },
{ id: 3, label: "Service Discovery Result send" },
{ id: 4, label: "Service Discovery Result received" },
],
},
{
groupName: "Service Operation",
groupId: 8,
groupColor: "rgb(135, 206, 235)",
groupIcon: <BuildIcon />,
messageTypes: [
{ id: 1, label: "Service Request Send" },
{ id: 2, label: "Service Request Received" },
{ id: 3, label: "Service Response Send" },
{ id: 4, label: "Service Response Received" },
],
},
],
};

View File

@@ -1,19 +1,6 @@
// DLG Summary Details
import { formatDateTime } from "@/utils/helpers"; import { formatDateTime } from "@/utils/helpers";
export const DLGSummaryDetails = [ // DLG - 2 Tables Configurations to display labels
{
label: "DID",
value: "did:sov:test:1274",
},
{
label: "URL",
value: "dlg.tu-berlin.de",
},
];
// DLG Resolution Table
export const DLGResolutionDummyData = [ export const DLGResolutionDummyData = [
{ {

View File

@@ -1,9 +0,0 @@
const BASE_URL = "http://localhost:2979/api/v1";
// Home View
const HOME_VIEW_TABLE = "/get_entities";
// Access Point
const SERVICE_REPOSITORY_URL = "/get_repositories";
export { BASE_URL, HOME_VIEW_TABLE, SERVICE_REPOSITORY_URL };

View File

@@ -1,7 +1,7 @@
export interface CustomTableConfiguration { export interface CustomTableConfiguration {
key: string; key: string;
label: string; label: string;
render?: (param: any) => void; render?: (param: any, rowData?: any, onConsume?: any) => void;
} }
export interface ICustomTable { export interface ICustomTable {
@@ -9,22 +9,27 @@ export interface ICustomTable {
data: any; data: any;
loading?: boolean; loading?: boolean;
tkey: string; tkey: string;
onConsumeAction?: (param: any) => void;
} }
export interface EntityDetails { export interface EntityDetails {
label: string; label: string;
value: string; value: string | undefined;
} }
export interface Entity { export interface Entity {
name: string; name?: string;
details: EntityDetails[]; details: EntityDetails[];
} }
export interface ISummaryDetails { export interface ISummaryDetails {
entity: any; entity: Entity;
fake?: boolean; fake?: boolean;
hasRefreshButton?: boolean; hasRefreshButton?: boolean;
hasAttachDetach?: boolean;
onRefresh?: () => void; onRefresh?: () => void;
} }
export interface IEntityActions {
name: string;
endpoint: string;
}

View File

@@ -1,5 +1,8 @@
export const formatDateTime = (date: string) => { import { projectConfig } from "@/config/config";
const _date = new Date(date);
export const formatDateTime = (date: string | number) => {
const dateToFormat = typeof date === "number" ? date * 1000 : date;
const _date = new Date(dateToFormat);
return _date.toLocaleDateString("en-US", { return _date.toLocaleDateString("en-US", {
year: "numeric", year: "numeric",
month: "long", month: "long",
@@ -10,3 +13,19 @@ export const formatDateTime = (date: string) => {
hour12: true, hour12: true,
}); });
}; };
export function sanitizeDID(did: string) {
return did.replace(/:/g, "_");
}
export function getGroupColor(groupName: string) {
const group = projectConfig.GROUPS.find(
(g: any) => g.groupName === groupName,
);
return group ? group.groupColor : "rgb(211, 211, 211)"; // Light gray if not found
}
export function getGroupById(groupId: string | number) {
const group = projectConfig.GROUPS.find((g: any) => g.groupId === groupId);
return group ? group : {};
}