generated from Luis/nextjs-python-web-template
Compare commits
61 Commits
9b6fc699f2
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
102e38988c | ||
|
|
4e7c44eeb5 | ||
|
|
3238cdf36d | ||
|
|
1abbc383ec | ||
| 5a09cc8e31 | |||
|
|
c6e1b8a21d | ||
|
|
afe291c54c | ||
| 6024090f69 | |||
|
|
596e87c31b | ||
|
|
06c55f151b | ||
|
|
04675f5e9e | ||
|
|
b5008306cb | ||
|
|
1b549549c0 | ||
|
|
29aa17ca7c | ||
|
|
e06afab048 | ||
|
|
697d2685f3 | ||
|
|
2f6ad476b3 | ||
|
|
dc04001dca | ||
|
|
3828402865 | ||
|
|
5b64eb4c3f | ||
|
|
eebc7eee20 | ||
|
|
2e787aa386 | ||
|
|
90c6df93f4 | ||
| 374fdfdaea | |||
|
|
a240cfd340 | ||
|
|
eaec0feb96 | ||
|
|
f3ab6e1b45 | ||
|
|
2c79dabce0 | ||
|
|
fd09d73edd | ||
|
|
2000c1444a | ||
|
|
b98c6090af | ||
| 42a25a2eda | |||
| b6b2bfbee5 | |||
| 09f80b1f42 | |||
| 170ada9382 | |||
| a8b472e84d | |||
|
|
cf3fc347de | ||
|
|
1d39ebcddc | ||
|
|
d978e413d5 | ||
| 8d72268922 | |||
|
|
840b3b6972 | ||
|
|
db1591a76e | ||
|
|
1d6ea0ef0c | ||
|
|
711ade4866 | ||
|
|
7779441c87 | ||
|
|
8e92415c34 | ||
|
|
68e2f6d683 | ||
|
|
c03da10e98 | ||
| 60205b3c22 | |||
| da99e71b54 | |||
| b978aabdd6 | |||
| 357568ebcb | |||
| ea2be8b7c7 | |||
| 672c8364bc | |||
| 2b8a9d9316 | |||
|
|
ea0148fdaf | ||
|
|
7ae1d5f768 | ||
|
|
ec67dd1bac | ||
|
|
522d7eb69a | ||
|
|
07a5a2fc24 | ||
|
|
59e33f3ead |
12
.gitlab-ci.yml
Normal file
12
.gitlab-ci.yml
Normal 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
|
||||||
@@ -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 if the Backend Changed
|
# Building a Docker Image if the Frontend Changed
|
||||||
|
|
||||||
To build a new docker image when the backend code changed 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,28 +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
|
|
||||||
|
|
||||||
## Building a Docker Image if the Frontend Changed
|
You can use the script:
|
||||||
|
|
||||||
To build a new docker image when the frontend 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. Then execute
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export GITLAB_TOKEN="<your-access-token>"
|
./push_docker.sh
|
||||||
```
|
|
||||||
|
|
||||||
Afterwards you can execute:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./build_docker.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### The Script Explained
|
### The Script Explained
|
||||||
|
|
||||||
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).
|
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
|
||||||
@@ -75,7 +132,7 @@ 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.
|
Upload ui-assets.tar.gz to gitlab.
|
||||||
@@ -112,45 +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
|
||||||
|
|
||||||
You can use the script:
|
To build a new docker image only when the backend code changed execute:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./push_docker.sh
|
nix build .#clan-docker
|
||||||
```
|
```
|
||||||
|
|
||||||
Login to the tu docker image server
|
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 login git.tu-berlin.de:5000
|
docker load < result
|
||||||
```
|
```
|
||||||
|
|
||||||
Tag the imported image
|
And then run the docker file 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 run -p 127.0.0.1:2979:2979 clan-docker:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Push the image to the git registry
|
- 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 image push git.tu-berlin.de:5000/internet-of-services-lab/service-aware-network-front-end:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
# 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
|
|
||||||
```
|
|
||||||
|
|
||||||
# Auto Generating a Python Client
|
# Auto Generating a Python Client
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# shellcheck shell=bash
|
|
||||||
set -euo pipefail
|
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
|
|
||||||
|
|
||||||
# Create a new ui build
|
|
||||||
nix build .#ui --out-link ui-release
|
|
||||||
tar -czvf ui-assets.tar.gz ui-release/lib/node_modules/clan-ui/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 ./ui-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)
|
PROJECT_DIR=$(git rev-parse --show-toplevel)
|
||||||
cat > "$PROJECT_DIR/pkgs/ui/nix/ui-assets.nix" <<EOF
|
"$PROJECT_DIR"/pkgs/clan-cli/upload_ui_assets.sh
|
||||||
{ fetchzip }:
|
|
||||||
fetchzip {
|
nix build .#clan-docker
|
||||||
url = "$url";
|
|
||||||
sha256 = "$(nix-prefetch-url --unpack $url)";
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
|
==============================
|
||||||
Please commit the changes to ui-assets.nix and push them to the repository.
|
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.
|
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 commit -m "Update ui-assets.nix" "$PROJECT_DIR/pkgs/ui/nix/ui-assets.nix"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -168,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)
|
||||||
@@ -386,9 +394,9 @@ def get_all_eventmessages(
|
|||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
result = cresult[cresult_idx]
|
result = cresult[cresult_idx]
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -125,8 +126,7 @@ random.seed(77)
|
|||||||
def create_eventmessages(num: int = 4) -> list[EventmessageCreate]:
|
def create_eventmessages(num: int = 4) -> list[EventmessageCreate]:
|
||||||
res = []
|
res = []
|
||||||
starttime = int(time.time())
|
starttime = int(time.time())
|
||||||
for idx in range(num):
|
for i2 in range(1, num + 1):
|
||||||
i2 = idx + 1
|
|
||||||
group_id = i2 % 5 + random.getrandbits(6) + 1
|
group_id = i2 % 5 + random.getrandbits(6) + 1
|
||||||
em_req_send = EventmessageCreate(
|
em_req_send = EventmessageCreate(
|
||||||
timestamp=starttime + i2 * 10,
|
timestamp=starttime + i2 * 10,
|
||||||
|
|||||||
48
pkgs/clan-cli/upload_ui_assets.sh
Executable file
48
pkgs/clan-cli/upload_ui_assets.sh
Executable 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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{ fetchzip }:
|
{ fetchzip }:
|
||||||
fetchzip {
|
fetchzip {
|
||||||
url = "https://gitea.gchq.icu/api/packages/IoSL/generic/IoSL-service-aware-frontend/0p1dw924f4sdkq26fd3rrb9qmryl84hdn5plz9ds105xv6al4ikf/assets.tar.gz";
|
url = "https://gitea.gchq.icu/api/packages/IoSL/generic/IoSL-service-aware-frontend/15svaig548jz1l8qsiqcycmw3hkb4805rb08mwlv2isxxshrj9ij/assets.tar.gz";
|
||||||
sha256 = "0p1dw924f4sdkq26fd3rrb9qmryl84hdn5plz9ds105xv6al4ikf";
|
sha256 = "15svaig548jz1l8qsiqcycmw3hkb4805rb08mwlv2isxxshrj9ij";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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} />;
|
|
||||||
}
|
|
||||||
@@ -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 },
|
||||||
|
{ label: "IP", value: entity?.ip },
|
||||||
|
{ label: "Network", value: entity?.network },
|
||||||
|
],
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<CardContent ref={cardContentRef}>
|
<div
|
||||||
<Typography color="text.primary" gutterBottom>
|
style={{
|
||||||
DID: <code>{entity?.did}</code>
|
display: "flex",
|
||||||
</Typography>
|
justifyContent: "space-between",
|
||||||
<Typography color="text.primary" gutterBottom>
|
flexWrap: "nowrap",
|
||||||
IP: <code>{entity?.ip}</code>
|
alignItems: "center",
|
||||||
</Typography>
|
}}
|
||||||
<Typography color="text.primary" gutterBottom>
|
>
|
||||||
Network: <code>{entity?.network}</code>
|
<div style={{ width: consumeContent ? "55%" : "100%" }}>
|
||||||
</Typography>
|
<h4>Service Consumer View</h4>
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
<div>
|
|
||||||
<h4>Client View</h4>
|
|
||||||
<CustomTable
|
<CustomTable
|
||||||
loading={services_loading}
|
loading={services_loading}
|
||||||
data={clients}
|
data={clients}
|
||||||
|
onConsumeAction={handleConsumeContent}
|
||||||
configuration={ClientTableConfig}
|
configuration={ClientTableConfig}
|
||||||
tkey="client-table"
|
tkey="client-table"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{consumeContent && (
|
||||||
|
<div style={{ width: "40%" }}>
|
||||||
|
<h4>Service Output</h4>
|
||||||
|
<ConsumeDisplayComponent htmlContent={consumeContent} />
|
||||||
|
</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}
|
||||||
|
|||||||
5
pkgs/ui/src/app/client/page.tsx
Normal file
5
pkgs/ui/src/app/client/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import Client from "@/app/client/client";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Client />;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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={{
|
||||||
|
|||||||
73
pkgs/ui/src/components/consume_action/index.tsx
Normal file
73
pkgs/ui/src/components/consume_action/index.tsx
Normal 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;
|
||||||
9
pkgs/ui/src/components/consume_content/index.tsx
Normal file
9
pkgs/ui/src/components/consume_content/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const ConsumeDisplayComponent = ({ htmlContent }: { htmlContent: any }) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div dangerouslySetInnerHTML={{ __html: htmlContent }} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConsumeDisplayComponent;
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
if (text) {
|
||||||
|
navigator.clipboard.writeText(copiedText).then(
|
||||||
|
() => {
|
||||||
setOpen(true);
|
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;
|
||||||
|
|||||||
73
pkgs/ui/src/components/copy_to_clipboard/readme.md
Normal file
73
pkgs/ui/src/components/copy_to_clipboard/readme.md
Normal 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;
|
||||||
|
```
|
||||||
231
pkgs/ui/src/components/entity_actions/index.tsx
Normal file
231
pkgs/ui/src/components/entity_actions/index.tsx
Normal 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;
|
||||||
49
pkgs/ui/src/components/hooks/useAxios.tsx
Normal file
49
pkgs/ui/src/components/hooks/useAxios.tsx
Normal 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;
|
||||||
@@ -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;
|
|
||||||
@@ -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: {},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
if (!loadingEventMessages && hasData) {
|
||||||
|
if (
|
||||||
|
currentMermaidRef &&
|
||||||
|
!currentMermaidRef.getAttribute("data-processed")
|
||||||
|
) {
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
startOnLoad: false,
|
startOnLoad: false,
|
||||||
securityLevel: "loose",
|
securityLevel: "loose",
|
||||||
sequence: {
|
sequence: {
|
||||||
mirrorActors: false,
|
mirrorActors: true,
|
||||||
|
showSequenceNumbers: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (mermaidRef.current) {
|
|
||||||
mermaidRef.current.innerHTML = mermaidString;
|
|
||||||
mermaid.init(undefined, mermaidRef.current);
|
|
||||||
}
|
}
|
||||||
}, [loadingEventMessages, hasData, mermaidString]);
|
|
||||||
|
if (currentMermaidRef) {
|
||||||
|
currentMermaidRef.innerHTML = mermaidString;
|
||||||
|
mermaid.init(undefined, currentMermaidRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,41 +172,62 @@ 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">
|
<div className="flex flex-col items-end">
|
||||||
{hasData ? (
|
{hasData ? (
|
||||||
<>
|
<>
|
||||||
<div className="flex justify-end">
|
<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">
|
<Tooltip placement="top" title="Refresh Diagram">
|
||||||
<IconButton color="default" onClick={onRefresh}>
|
<IconButton color={iconButtonColor} onClick={onRefresh}>
|
||||||
<RefreshIcon />
|
<RefreshIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Zoom In" placement="top">
|
<Tooltip title="Zoom In" placement="top">
|
||||||
<IconButton color="primary" onClick={zoomIn}>
|
<IconButton color={iconButtonColor} onClick={zoomIn}>
|
||||||
<ZoomInIcon />
|
<ZoomInIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Zoom Out" placement="top">
|
<Tooltip title="Zoom Out" placement="top">
|
||||||
<IconButton color="primary" onClick={zoomOut}>
|
<IconButton color={iconButtonColor} onClick={zoomOut}>
|
||||||
<ZoomOutIcon />
|
<ZoomOutIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Reset" placement="top">
|
<Tooltip title="Reset" placement="top">
|
||||||
<IconButton color="primary" onClick={resetZoom}>
|
<IconButton color={iconButtonColor} onClick={resetZoom}>
|
||||||
<ResetIcon />
|
<ResetIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="View in Fullscreen" placement="top">
|
<Tooltip title="View in Fullscreen" placement="top">
|
||||||
<IconButton color="primary" onClick={viewInFullScreen}>
|
<IconButton color={iconButtonColor} onClick={viewInFullScreen}>
|
||||||
<FullscreenIcon />
|
<FullscreenIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Download as PNG" placement="top">
|
<Tooltip title="Download as PNG" placement="top">
|
||||||
<IconButton color="primary" onClick={downloadAsPng}>
|
<IconButton color={iconButtonColor} onClick={downloadAsPng}>
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@@ -175,6 +242,117 @@ const SequenceDiagram = () => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{openFilters && (
|
||||||
|
<>
|
||||||
|
<Dialog
|
||||||
|
open={openFilters}
|
||||||
|
keepMounted
|
||||||
|
fullWidth
|
||||||
|
maxWidth="lg"
|
||||||
|
onClose={toggleFilters}
|
||||||
|
>
|
||||||
|
<DialogTitle>All Event Messages</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
<div className="flex items-center gap-2.5">
|
||||||
|
<label>Search by Sequence # </label>
|
||||||
|
<TextField
|
||||||
|
onChange={onSearchBySeqNumber}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<List className="w-full" component="nav">
|
||||||
|
{allEventMessages
|
||||||
|
.filter((_: any, index: number) => {
|
||||||
|
return isFilterMatch(index);
|
||||||
|
})
|
||||||
|
.map((message: any, index: number) => {
|
||||||
|
const {
|
||||||
|
msg_type_name: msgType,
|
||||||
|
des_name,
|
||||||
|
src_name,
|
||||||
|
group,
|
||||||
|
group_id,
|
||||||
|
timestamp,
|
||||||
|
src_did,
|
||||||
|
des_did,
|
||||||
|
// msg, TODO: Need to use the content inside the msg to display in the diagram
|
||||||
|
} = 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>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useGetAllEntities } from "@/api/entities/entities";
|
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";
|
||||||
|
|
||||||
@@ -34,8 +35,6 @@ type MenuEntry = {
|
|||||||
subMenuEntries?: MenuEntry[];
|
subMenuEntries?: MenuEntry[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export let menuEntityEntries: MenuEntry[] = [];
|
|
||||||
|
|
||||||
export const menuEntries: MenuEntry[] = [
|
export const menuEntries: MenuEntry[] = [
|
||||||
{
|
{
|
||||||
icon: <HomeIcon />,
|
icon: <HomeIcon />,
|
||||||
@@ -72,7 +71,9 @@ interface SidebarProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Sidebar(props: SidebarProps) {
|
export function Sidebar(props: SidebarProps) {
|
||||||
const { data: entityData } = useGetAllEntities();
|
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 : "",
|
||||||
@@ -89,17 +90,22 @@ export function Sidebar(props: SidebarProps) {
|
|||||||
setCollapseMenuOpen(!collapseMenuOpen);
|
setCollapseMenuOpen(!collapseMenuOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
const menuEntityEntries: MenuEntry[] = React.useMemo(() => {
|
||||||
if (entityData) {
|
if (entityData) {
|
||||||
menuEntityEntries = Array.isArray(entityData.data)
|
return Array.isArray(entityData.data)
|
||||||
? entityData.data.map((entity) => ({
|
? entityData.data.map((entity: any) => ({
|
||||||
icon: <PersonIcon />,
|
icon: <PersonIcon />,
|
||||||
label: entity.name,
|
label: entity.name,
|
||||||
to: `/client/${entity.name}`,
|
to: entity.name,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
}))
|
}))
|
||||||
: [];
|
: [];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
}, [entityData]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
if (isSmallerScreen) {
|
if (isSmallerScreen) {
|
||||||
setCollapseMenuOpen(false);
|
setCollapseMenuOpen(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -201,12 +207,16 @@ export function Sidebar(props: SidebarProps) {
|
|||||||
>
|
>
|
||||||
<List component="div" disablePadding>
|
<List component="div" disablePadding>
|
||||||
{menuEntityEntries?.map((menuEntry, idx) => (
|
{menuEntityEntries?.map((menuEntry, idx) => (
|
||||||
|
<Link
|
||||||
|
key={"entity-link-" + idx}
|
||||||
|
href={`/client?name=${menuEntry.to}`}
|
||||||
|
style={{ textDecoration: "none", color: "white" }}
|
||||||
|
>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
key={idx}
|
key={idx}
|
||||||
sx={{ pl: 4 }}
|
sx={{ pl: 4 }}
|
||||||
className="lg:justify-normal"
|
className="lg:justify-normal"
|
||||||
LinkComponent={Link}
|
LinkComponent={Link}
|
||||||
href={menuEntry.to}
|
|
||||||
disabled={menuEntry.disabled}
|
disabled={menuEntry.disabled}
|
||||||
selected={activeMenuItem === menuEntry.to}
|
selected={activeMenuItem === menuEntry.to}
|
||||||
onClick={() => handleMenuItemClick(menuEntry.to)}
|
onClick={() => handleMenuItemClick(menuEntry.to)}
|
||||||
@@ -225,6 +235,7 @@ export function Sidebar(props: SidebarProps) {
|
|||||||
className="hidden lg:block"
|
className="hidden lg:block"
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
|
</Link>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
52
pkgs/ui/src/components/summary_card/readme.md
Normal file
52
pkgs/ui/src/components/summary_card/readme.md
Normal 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
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
@@ -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,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
|
|||||||
75
pkgs/ui/src/components/table/readme.md
Normal file
75
pkgs/ui/src/components/table/readme.md
Normal 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;
|
||||||
|
```
|
||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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;
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
112
pkgs/ui/src/config/config.tsx
Normal file
112
pkgs/ui/src/config/config.tsx
Normal 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" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -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 = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 };
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 : {};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user