diff --git a/.gitlab/ci/.build-binaries.yml b/.gitlab/ci/.build-binaries.yml index 4877cd1697d13de412485f281f2bf2b46a8b1019..174203fdcd52622712b38ef0718612d6cf873a94 100644 --- a/.gitlab/ci/.build-binaries.yml +++ b/.gitlab/ci/.build-binaries.yml @@ -16,6 +16,5 @@ build-all-binaries: - artifacts/venv-manager - artifacts/inventory-manager - artifacts/plugin-registry - - artifacts/react-ui expire_in: 1 week <<: *build-binaries diff --git a/.gitlab/ci/.build-container-images.yml b/.gitlab/ci/.build-container-images.yml index 7fda52178317dd8ff0d4a0bcc0f6eda809addf8a..da70dcba2d6ae122b967c2417c014750cbed26b0 100644 --- a/.gitlab/ci/.build-container-images.yml +++ b/.gitlab/ci/.build-container-images.yml @@ -75,16 +75,6 @@ build-inventory-manager-image: - docker push "$INVENTORY_MANAGER_IMAGE_NAME:$CI_COMMIT_REF_SLUG" <<: *build -build-react-ui-image: - script: - - REACT_UI_IMAGE_NAME="${CI_REGISTRY_IMAGE}/react-ui" - - docker buildx build -t "$REACT_UI_IMAGE_NAME:$CI_COMMIT_SHA" -f "${CI_PROJECT_DIR}/react-ui/docker/webserver/Dockerfile" . - - docker push "$REACT_UI_IMAGE_NAME:$CI_COMMIT_SHA" - - docker tag "$REACT_UI_IMAGE_NAME:$CI_COMMIT_SHA" "$REACT_UI_IMAGE_NAME:$CI_COMMIT_REF_SLUG" - - docker push "$REACT_UI_IMAGE_NAME:$CI_COMMIT_REF_SLUG" - <<: *build - - build-integration-test-images: needs: ["build-controller-image"] script: diff --git a/.gitlab/ci/.code-quality-ci.yml b/.gitlab/ci/.code-quality-ci.yml index 95ed3abe96e1220d751c05e5e16224a29e47b99d..d054508c42f8f2a9bca5020a40c09aebd06516d5 100644 --- a/.gitlab/ci/.code-quality-ci.yml +++ b/.gitlab/ci/.code-quality-ci.yml @@ -1,5 +1,5 @@ code-quality: - image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:v1.63.4-alpine + image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:v1.62.2-alpine stage: analyze script: # writes golangci-lint output to gl-code-quality-report.json diff --git a/.gitlab/ci/.react-ui.yml b/.gitlab/ci/.react-ui.yml new file mode 100644 index 0000000000000000000000000000000000000000..873b694c892b39870eb60ad629f836055089734b --- /dev/null +++ b/.gitlab/ci/.react-ui.yml @@ -0,0 +1,3 @@ +build-react-ui: + stage: build + \ No newline at end of file diff --git a/.gitlab/ci/.renovate.yml b/.gitlab/ci/.renovate.yml index 81070e9da254d8199c4a375c34c1bfcd3e62d710..55c8ea3356625716b25ede02abb096628ee318cf 100644 --- a/.gitlab/ci/.renovate.yml +++ b/.gitlab/ci/.renovate.yml @@ -1,7 +1,7 @@ renovate: stage: tools - image: renovate/renovate:39.107.0 + image: renovate/renovate:39.48.1 variables: LOG_LEVEL: debug diff --git a/.golangci.yml b/.golangci.yml index 42bb6ac6b31f1abf94ecc1e47bcf910873a99dc3..cdd404e55c3ed172a14d18e70ece0ec4be7ebe53 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -14,13 +14,13 @@ output: - format: colored-line-number print-issued-lines: true print-linter-name: true + uniq-by-line: true path-prefix: "" issues: exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 0 - uniq-by-line: true exclude-files: - http.go # directories to be ignored by linters diff --git a/Makefile b/Makefile index 693329772ec396aada21e43a6586306b8949af60..888644abed5135d8bf2364302499eefd2f2ea0e4 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ PLUGIN_NAME= bundled_plugin.zip # Tool Versions GOTESTSUM_VERSION=v1.8.1 -GOLANGCI_LINT_VERSION=v1.63.4 +GOLANGCI_LINT_VERSION=v1.62.0 MOCKERY_VERSION=v2.20.0 YGOT_GENERATOR_VERSION=v0.27.0 YGOT_GENERATOR_GENERATOR_VERSION=v0.0.4 @@ -60,7 +60,7 @@ lint-fix: install-tools build: pre build-gosdn build-gosdnc build-plugin-registry build-venv-manager build-arista-routing-engine-app build-hostname-checker-app build-basic-interface-monitoring-app build-inventory-manager -containerize-all: containerize-gosdn containerize-gosdnc containerize-plugin-registry containerize-venv-manager containerize-arista-routing-engine-app containerize-inventory-manager containerize-react-ui +containerize-all: containerize-gosdn containerize-gosdnc containerize-plugin-registry containerize-venv-manager containerize-arista-routing-engine-app containerize-inventory-manager generate-all-certs: pre generate-root-ca generate-gosdn-certs generate-gnmi-target-certs diff --git a/controller/northbound/server/auth_test.go b/controller/northbound/server/auth_test.go index 956ef2be19b5da9be5eebe2636763bb7c91fe7b5..e5c9e7389d443c0f653f53a3c5f32966f5c2671d 100644 --- a/controller/northbound/server/auth_test.go +++ b/controller/northbound/server/auth_test.go @@ -89,13 +89,7 @@ func TestAuth_Login(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("username"), - }, - }, - }, + FieldPath: stringToPointer("username"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }}, @@ -166,13 +160,7 @@ func TestAuth_Logout(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("username"), - }, - }, - }, + FieldPath: stringToPointer("username"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }}, diff --git a/controller/northbound/server/role_test.go b/controller/northbound/server/role_test.go index 2d75e067843f15c594d6d34e2b0a503d058c8582..bb787a4160530b0454d4b2c0198f47c0f9a98ffc 100644 --- a/controller/northbound/server/role_test.go +++ b/controller/northbound/server/role_test.go @@ -84,20 +84,10 @@ func TestRole_CreateRoles(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("roles"), - }, - { - FieldName: stringToPointer("name"), - }, - }, - }, + FieldPath: stringToPointer("roles[0].name"), ConstraintId: stringToPointer("string.min_len"), Message: stringToPointer("value length must be at least 3 characters"), - }, - }, + }}, }, { name: "role with too short description should fail", @@ -115,16 +105,7 @@ func TestRole_CreateRoles(t *testing.T) { want: &apb.CreateRolesResponse{}, wantErr: true, validationErrors: []*validate.Violation{{ - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("roles"), - }, - { - FieldName: stringToPointer("description"), - }, - }, - }, + FieldPath: stringToPointer("roles[0].description"), ConstraintId: stringToPointer("string.min_len"), Message: stringToPointer("value length must be at least 3 characters"), }}, @@ -200,13 +181,7 @@ func TestRole_GetRole(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("role_name"), - }, - }, - }, + FieldPath: stringToPointer("role_name"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }, @@ -380,16 +355,7 @@ func TestRole_UpdateRoles(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("roles"), - }, - { - FieldName: stringToPointer("name"), - }, - }, - }, + FieldPath: stringToPointer("roles[0].name"), ConstraintId: stringToPointer("string.min_len"), Message: stringToPointer("value length must be at least 3 characters"), }, @@ -413,16 +379,7 @@ func TestRole_UpdateRoles(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("roles"), - }, - { - FieldName: stringToPointer("description"), - }, - }, - }, + FieldPath: stringToPointer("roles[0].description"), ConstraintId: stringToPointer("string.min_len"), Message: stringToPointer("value length must be at least 3 characters"), }, @@ -499,24 +456,12 @@ func TestRole_DeletePermissionsForRole(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("role_name"), - }, - }, - }, + FieldPath: stringToPointer("role_name"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }, { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("permissions_to_delete"), - }, - }, - }, + FieldPath: stringToPointer("permissions_to_delete"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }, diff --git a/controller/northbound/server/topology_test.go b/controller/northbound/server/topology_test.go index dea560a452ff02ff710d6ca71fcc251d8f57472d..44ce77b57b19c31d5cc6292e5af5c2ac265e3a42 100644 --- a/controller/northbound/server/topology_test.go +++ b/controller/northbound/server/topology_test.go @@ -285,72 +285,27 @@ func TestTopology_AddLink(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("link"), - }, - { - FieldName: stringToPointer("name"), - }, - }, - }, + FieldPath: stringToPointer("link.name"), ConstraintId: stringToPointer("string.min_len"), Message: stringToPointer("value length must be at least 1 characters"), }, { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("link"), - }, - { - FieldName: stringToPointer("sourceNode"), - }, - }, - }, + FieldPath: stringToPointer("link.sourceNode"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }, { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("link"), - }, - { - FieldName: stringToPointer("targetNode"), - }, - }, - }, + FieldPath: stringToPointer("link.targetNode"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }, { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("link"), - }, - { - FieldName: stringToPointer("sourcePort"), - }, - }, - }, + FieldPath: stringToPointer("link.sourcePort"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }, { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("link"), - }, - { - FieldName: stringToPointer("targetPort"), - }, - }, - }, + FieldPath: stringToPointer("link.targetPort"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }, @@ -506,13 +461,7 @@ func TestTopology_DeleteLink(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("id"), - }, - }, - }, + FieldPath: stringToPointer("id"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }}, diff --git a/controller/northbound/server/user_test.go b/controller/northbound/server/user_test.go index 9fcdf4d610cb5685e69e231bd2d65cde95a7e9e8..4a1c663279ce5607a40ff3dd1fd8d87f03d7e0bf 100644 --- a/controller/northbound/server/user_test.go +++ b/controller/northbound/server/user_test.go @@ -90,16 +90,7 @@ func TestUser_CreateUsers(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("user"), - }, - { - FieldName: stringToPointer("password"), - }, - }, - }, + FieldPath: stringToPointer("user[0].password"), ConstraintId: stringToPointer("string.min_len"), Message: stringToPointer("value length must be at least 5 characters"), }}, @@ -125,16 +116,7 @@ func TestUser_CreateUsers(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("user"), - }, - { - FieldName: stringToPointer("name"), - }, - }, - }, + FieldPath: stringToPointer("user[0].name"), ConstraintId: stringToPointer("string.min_len"), Message: stringToPointer("value length must be at least 3 characters"), }}, @@ -208,13 +190,7 @@ func TestUser_GetUser(t *testing.T) { wantErr: true, validationErrors: []*validate.Violation{ { - Field: &validate.FieldPath{ - Elements: []*validate.FieldPathElement{ - { - FieldName: stringToPointer("name"), - }, - }, - }, + FieldPath: stringToPointer("name"), ConstraintId: stringToPointer("required"), Message: stringToPointer("value is required"), }}, diff --git a/controller/northbound/server/utils_test.go b/controller/northbound/server/utils_test.go index dd95d8f2e8c56ba51bf01ade6d6cf5efa0831758..7a4636caabf0f8d66d6e36383e59026d2f376315 100644 --- a/controller/northbound/server/utils_test.go +++ b/controller/northbound/server/utils_test.go @@ -23,8 +23,7 @@ func isEqualFieldPaths(violationFieldPath, errFieldPath *validate.FieldPath) boo } for i, elem := range violationFieldPath.GetElements() { - errElem := errFieldPath.GetElements()[i] - if *elem.FieldName != *errElem.FieldName { + if elem != errFieldPath.GetElements()[i] { return false } } diff --git a/controller/nucleus/networkElementWatcher.go b/controller/nucleus/networkElementWatcher.go index 07ad775f253c361a7921c6699d95b2681c1e7002..8b542d086ca5c61e7fb5d776137c6c11260fd858 100644 --- a/controller/nucleus/networkElementWatcher.go +++ b/controller/nucleus/networkElementWatcher.go @@ -200,8 +200,6 @@ func (n *NetworkElementWatcher) StopAndRemoveNetworkElementSubscription(subID uu // handleSubscribeResponse takes the subscribe response and additional information about the network element to distinguish // from which network element a subscribe response was sent including improved error handling. func (n *NetworkElementWatcher) handleSubscribeResponse(subscriptionInfo *transport.SubscriptionInformation, workerName string) { - log.Debugf("Received Subscribe response: MNE ID: %s, MNE Name: %s, SubResponse: %v", subscriptionInfo.NetworkElementID, subscriptionInfo.NetworkElementName, subscriptionInfo.SubResponse) - if subscriptionInfo.SubResponse == nil { // Note: This needs proper error handling, no idea how yet. Simply logging would lead to spam in the console // if the target that was subscribed to is not reachable anymore. @@ -234,11 +232,6 @@ func (n *NetworkElementWatcher) handleSubscribeResponse(subscriptionInfo *transp func (n *NetworkElementWatcher) handleSubscribeResponseUpdate(resp *gpb.SubscribeResponse_Update, subscriptionInfo *transport.SubscriptionInformation) { pathsAndValues := make(map[string]string, len(resp.Update.Update)) - if resp.Update == nil || len(resp.Update.Update) == 0 { - log.Debugf("handleSubscribeResponseUpdate empty update or updates; Update: %v, InnerUpdates: %v", resp.Update, resp.Update.Update) - return - } - for _, update := range resp.Update.Update { pathString, err := ygot.PathToString(update.Path) if err != nil { diff --git a/dev_env_data/clab/gosdn_slim.clab.yaml b/dev_env_data/clab/gosdn_slim.clab.yaml index b3ef8eb088890c2e63d349c11d4d7de5f60a5e65..f4d76846cddf22b054f8cee3c4e39d70589cb7ea 100644 --- a/dev_env_data/clab/gosdn_slim.clab.yaml +++ b/dev_env_data/clab/gosdn_slim.clab.yaml @@ -13,13 +13,6 @@ topology: image: plugin-registry mgmt-ipv4: 172.100.0.16 - react-ui: - kind: linux - image: react-ui - ports: - - 127.0.0.1:8088:80 - mgmt-ipv4: 172.100.0.6 - gosdn: kind: linux image: gosdn diff --git a/dev_env_data/docker-compose/basic_docker-compose.yml b/dev_env_data/docker-compose/basic_docker-compose.yml index 2544e7b0f3728d57d64b3138dd7f5d6f74f7f4cc..4e7664161a7a65487281e203f52d866271815d7c 100644 --- a/dev_env_data/docker-compose/basic_docker-compose.yml +++ b/dev_env_data/docker-compose/basic_docker-compose.yml @@ -75,10 +75,5 @@ services: command: start --cert /etc/gnmi-target/ssl/certs/gnmi-target-selfsigned.crt --key /etc/gnmi-target/ssl/private/gnmi-target-selfsigned.key --ca_file /etc/gnmi-target/ssl/ca.crt - react-ui: - image: react-ui - ports: - - 127.0.0.1:8088:80 - volumes: mongo-db-basic: diff --git a/go.mod b/go.mod index f482650864f050c1a0a4805656c6778dbff70cc4..2879fe71145322dbb2465f3d4479af08aec81257 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,14 @@ module code.fbi.h-da.de/danet/gosdn go 1.23 require ( - github.com/aristanetworks/goarista v0.0.0-20250108234106-1f88a86e2265 + github.com/aristanetworks/goarista v0.0.0-20241115153057-bd75d7f26a44 github.com/c-bata/go-prompt v0.2.6 github.com/docker/docker v24.0.9+incompatible github.com/google/go-cmp v0.6.0 github.com/google/uuid v1.6.0 - github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 github.com/mitchellh/go-homedir v1.1.0 - github.com/openconfig/gnmi v0.12.0 + github.com/openconfig/gnmi v0.11.0 github.com/openconfig/goyang v1.6.0 github.com/openconfig/ygot v0.29.20 github.com/prometheus/client_golang v1.20.5 @@ -22,10 +22,10 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.10.0 - go.mongodb.org/mongo-driver/v2 v2.0.0 + go.mongodb.org/mongo-driver v1.17.1 golang.org/x/sync v0.10.0 - google.golang.org/grpc v1.69.4 - google.golang.org/protobuf v1.36.2 + google.golang.org/grpc v1.68.1 + google.golang.org/protobuf v1.35.2 gopkg.in/yaml.v3 v3.0.1 ) @@ -41,13 +41,13 @@ require ( github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible - github.com/golang/glog v1.2.4 + github.com/golang/glog v1.2.3 github.com/golang/protobuf v1.5.4 github.com/golang/snappy v0.0.4 // indirect github.com/gookit/color v1.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -64,7 +64,7 @@ require ( github.com/pkg/term v1.2.0-beta.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.61.0 // indirect + github.com/prometheus/common v0.57.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rabbitmq/amqp091-go v1.10.0 github.com/rivo/uniseg v0.4.4 // indirect @@ -77,22 +77,21 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - golang.org/x/crypto v0.32.0 - golang.org/x/net v0.34.0 - golang.org/x/sys v0.29.0 // indirect - golang.org/x/term v0.28.0 // indirect + golang.org/x/crypto v0.30.0 + golang.org/x/net v0.31.0 + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) require ( - buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.2-20241127180247-a33202765966.1 - github.com/bufbuild/protovalidate-go v0.8.2 + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20241127180247-a33202765966.1 + github.com/bufbuild/protovalidate-go v0.7.3 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-plugin v1.4.10 github.com/lesismal/nbio v1.5.12 - go.mongodb.org/mongo-driver v1.17.2 - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 + google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a ) require ( @@ -104,7 +103,7 @@ require ( github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/containerd/console v1.0.3 // indirect github.com/fatih/color v1.15.0 // indirect - github.com/google/cel-go v0.22.1 // indirect + github.com/google/cel-go v0.22.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -123,7 +122,7 @@ require ( github.com/stoewer/go-strcase v1.3.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect + golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/go.sum b/go.sum index 2fc1f0098212c87292e3a462639a30cf266a8709..880acc9d162c50002ebb01111d0ee6477af85d60 100644 --- a/go.sum +++ b/go.sum @@ -16,10 +16,6 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-2024092016423 buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20240920164238-5a7b106cbb87.1/go.mod h1:mnHCFccv4HwuIAOHNGdiIc5ZYbBCvbTWZcodLN5wITI= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20241127180247-a33202765966.1 h1:jLd96rDDNJ+zIJxvV/L855VEtrjR0G4aePVDlCpf6kw= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.35.2-20241127180247-a33202765966.1/go.mod h1:mnHCFccv4HwuIAOHNGdiIc5ZYbBCvbTWZcodLN5wITI= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.1-20241127180247-a33202765966.1 h1:v223wh/bhlSHSc0tU9PXRWXHhkw3UWMtth7TmYGfHAQ= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.1-20241127180247-a33202765966.1/go.mod h1:/zlFuuECgFgewxwW6qQKgvMJ07YZkWlVkcSxEhJprJw= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.2-20241127180247-a33202765966.1 h1:BICM6du/XzvEgeorNo4xgohK3nMTmEPViGyd5t7xVqk= -buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.2-20241127180247-a33202765966.1/go.mod h1:JnMVLi3qrNYPODVpEKG7UjHLl/d2zR221e66YCSmP2Q= cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -60,10 +56,6 @@ github.com/aristanetworks/goarista v0.0.0-20241101122619-a6d58bf1ed81 h1:CpeoPCo github.com/aristanetworks/goarista v0.0.0-20241101122619-a6d58bf1ed81/go.mod h1:C+YeQrhbMvCPh5wG6iqGiCD/zcITTpt4YQ1v4K0g5Vc= github.com/aristanetworks/goarista v0.0.0-20241115153057-bd75d7f26a44 h1:vb3HPPa1CegMZY90JF8mDyxXiV+qJJuSWwMhBZCcsws= github.com/aristanetworks/goarista v0.0.0-20241115153057-bd75d7f26a44/go.mod h1:C+YeQrhbMvCPh5wG6iqGiCD/zcITTpt4YQ1v4K0g5Vc= -github.com/aristanetworks/goarista v0.0.0-20250108214730-362a04c9d029 h1:bvw2TILeXtuYfZ9rip/4DY933UuIvCwtvJmwvz978ac= -github.com/aristanetworks/goarista v0.0.0-20250108214730-362a04c9d029/go.mod h1:C+YeQrhbMvCPh5wG6iqGiCD/zcITTpt4YQ1v4K0g5Vc= -github.com/aristanetworks/goarista v0.0.0-20250108234106-1f88a86e2265 h1:NPQhasGGtAIxtDG4KQTcQviV9T6a98kbKSO0VKFRS+E= -github.com/aristanetworks/goarista v0.0.0-20250108234106-1f88a86e2265/go.mod h1:1xldiSdHhqa1XIr6EPNnSBfwZEAMZwwJIiEtMS8yzkU= github.com/aristanetworks/gomap v0.0.0-20240724180630-b4cffb90720f h1:3GwV1IeLp0PwWcnbc9ZihE3osvexJf3PMjWSCGjtIqc= github.com/aristanetworks/gomap v0.0.0-20240724180630-b4cffb90720f/go.mod h1:bNzH6HFWav8D/ws3QlkjLpf9ZOdsUTDx+qJikWCcGRc= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= @@ -79,8 +71,6 @@ github.com/bufbuild/protovalidate-go v0.7.2 h1:UuvKyZHl5p7u3ztEjtRtqtDxOjRKX5VUO github.com/bufbuild/protovalidate-go v0.7.2/go.mod h1:PHV5pFuWlRzdDW02/cmVyNzdiQ+RNNwo7idGxdzS7o4= github.com/bufbuild/protovalidate-go v0.7.3 h1:kKnoSueygR3xxppvuBpm9SEwIsP359MMRfMBGmRByPg= github.com/bufbuild/protovalidate-go v0.7.3/go.mod h1:CFv34wMqiBzAHdQ4q/tWYi9ILFYKuaC3/4zh6eqdUck= -github.com/bufbuild/protovalidate-go v0.8.2 h1:sgzXHkHYP6HnAsL2Rd3I1JxkYUyEQUv9awU1PduMxbM= -github.com/bufbuild/protovalidate-go v0.8.2/go.mod h1:K6w8iPNAXBoIivVueSELbUeUl+MmeTQfCDSug85pn3M= github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= @@ -134,8 +124,6 @@ github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/glog v1.2.3 h1:oDTdz9f5VGVVNGu/Q7UXKWYsD0873HXLHdJUNBsSEKM= github.com/golang/glog v1.2.3/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= -github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -158,8 +146,6 @@ github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= -github.com/google/cel-go v0.22.1 h1:AfVXx3chM2qwoSbM7Da8g8hX8OVSkBFwX+rz2+PcK40= -github.com/google/cel-go v0.22.1/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -183,8 +169,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -206,8 +190,6 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -280,8 +262,6 @@ github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DV github.com/openconfig/gnmi v0.10.0/go.mod h1:Y9os75GmSkhHw2wX8sMsxfI7qRGAEcDh8NTa5a8vj6E= github.com/openconfig/gnmi v0.11.0 h1:H7pLIb/o3xObu3+x0Fv9DCK7TH3FUh7mNwbYe+34hFw= github.com/openconfig/gnmi v0.11.0/go.mod h1:9oJSQPPCpNvfMRj8e4ZoLVAw4wL8HyxXbiDlyuexCGU= -github.com/openconfig/gnmi v0.12.0 h1:aPkmcX9pdcz6QqsBsXXg5UQooqhnmlHD3JtdtvtzmaU= -github.com/openconfig/gnmi v0.12.0/go.mod h1:5a/cIOZevJLfJgd1qWkgYROE8xfgEbaSJXpdD8xk/LQ= github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea/go.mod h1:dhXaV0JgHJzdrHi2l+w0fZrwArtXL7jEFoiqLEdmkvU= github.com/openconfig/goyang v1.6.0 h1:JjnPbLY1/y28VyTO67LsEV0TaLWNiZyDcsppGq4F4is= github.com/openconfig/goyang v1.6.0/go.mod h1:sdNZi/wdTZyLNBNfgLzmmbi7kISm7FskMDKKzMY+x1M= @@ -316,8 +296,6 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY= github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI= -github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= -github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/protocolbuffers/txtpbfmt v0.0.0-20220608084003-fc78c767cd6a/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78= @@ -412,9 +390,6 @@ go.mongodb.org/mongo-driver v1.17.0 h1:Hp4q2MCjvY19ViwimTs00wHi7G4yzxh4/2+nTx8r4 go.mongodb.org/mongo-driver v1.17.0/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHyIM= go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4= -go.mongodb.org/mongo-driver v1.17.2 h1:gvZyk8352qSfzyZ2UMWcpDpMSGEr1eqE4T793SqyhzM= -go.mongodb.org/mongo-driver v1.17.2/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= -go.mongodb.org/mongo-driver/v2 v2.0.0/go.mod h1:nSjmNq4JUstE8IRZKTktLgMHM4F1fccL6HGX1yh+8RA= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -436,15 +411,9 @@ golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7 h1:wDLEX9a7YQoKdKNQt88rtydkqDxeGaBUTnIYc3iG/mA= golang.org/x/exp v0.0.0-20240716175740-e3f259677ff7/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c h1:7dEasQXItcW1xKJ2+gg5VOiBnqWrJc+rq0DPKyvvdbY= -golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:NQtJDoLvd6faHhE7m4T/1IY708gDefGGjR/iUW8yQQ8= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -477,10 +446,6 @@ golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -536,8 +501,6 @@ golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -551,8 +514,6 @@ golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -614,10 +575,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1: google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88= google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E= google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY= -google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d h1:H8tOf8XM88HvKqLTxe755haY6r1fqqzLbEnfrmLXlSA= -google.golang.org/genproto/googleapis/api v0.0.0-20250102185135-69823020774d/go.mod h1:2v7Z7gP2ZUOGsaFyxATQSRoBnKygqVq2Cwnvom7QiqY= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= @@ -638,12 +595,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d h1:xJJRGY7TJcvIlpSrN3K6LAWgNFUILlO+OMAqtg9aqnw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250102185135-69823020774d/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -662,10 +613,6 @@ google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= -google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= -google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= -google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -686,10 +633,6 @@ google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFyt google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= -google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/makefiles/container/Makefile b/makefiles/container/Makefile index 6e3b97242bdd968d9e4236440721c9bea30bc805..c9c21b9728af4606335ea425a187a55dd09f5ad1 100644 --- a/makefiles/container/Makefile +++ b/makefiles/container/Makefile @@ -25,6 +25,3 @@ containerize-ws-events-app: containerize-inventory-manager: docker buildx build --rm -t venv-manager --load -f applications/inventory-manager/inventory-manager.Dockerfile . - -containerize-react-ui: - docker buildx build --rm --load -t react-ui -f react-ui/docker/webserver/Dockerfile . diff --git a/plugin-registry/plugin-registry.Dockerfile b/plugin-registry/plugin-registry.Dockerfile index 292a134fd5707a0508ad136b8a01d8e2f3d0890d..05d3aad83fd593051616bfc5345367d961f8adeb 100644 --- a/plugin-registry/plugin-registry.Dockerfile +++ b/plugin-registry/plugin-registry.Dockerfile @@ -4,9 +4,8 @@ ARG GITLAB_PROXY FROM ${GITLAB_PROXY}golang:$GOLANG_VERSION-bookworm as builder WORKDIR /plugin-registry/ -RUN apt-get update && \ - apt-get -y install --no-install-recommends zip && \ - rm -rf /var/lib/apt/lists/* +RUN apt-get update +RUN apt-get -y install --no-install-recommends zip COPY . . RUN --mount=type=cache,target=/root/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ @@ -22,4 +21,4 @@ WORKDIR /app/ COPY --from=builder /plugin-registry/artifacts/plugin-registry . COPY --from=builder /plugin-registry/plugin-registry/plugins ./plugins COPY --from=builder /plugin-registry/dev_env_data/plugin-registry/plugin-store.json ./plugin-store.json -ENTRYPOINT ["./plugin-registry", "-socket", "55057"] \ No newline at end of file +ENTRYPOINT ["./plugin-registry", "-socket", "55057"] diff --git a/react-ui/.dockerignore b/react-ui/.dockerignore deleted file mode 100644 index 5ecec04724f8685ae46c6f5ddf66a4c92844754f..0000000000000000000000000000000000000000 --- a/react-ui/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -dist/ -vite/ -tmp/ -.vscode/ \ No newline at end of file diff --git a/react-ui/assets/logo.svg b/react-ui/assets/logo.svg new file mode 100755 index 0000000000000000000000000000000000000000..b7f71bd907d8ab0aa829f03519d4d6f06cdadfec --- /dev/null +++ b/react-ui/assets/logo.svg @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<!-- Created with Vectornator (http://vectornator.io/) --> +<svg height="100%" stroke-miterlimit="10" style="fill-rule:nonzero;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;" version="1.1" viewBox="0 0 113.4 212.625" width="100%" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:vectornator="http://vectornator.io" xmlns:xlink="http://www.w3.org/1999/xlink"> +<defs> +<radialGradient cx="170.235" cy="146.046" gradientTransform="matrix(1.00001 -8.65109e-05 8.65119e-05 1 -115.465 -116.986)" gradientUnits="userSpaceOnUse" id="RadialGradient" r="217.591"> +<stop offset="0" stop-color="#c456f7"/> +<stop offset="1" stop-color="#34054a"/> +</radialGradient> +<filter color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse" height="209.692" id="Filter" width="111.957" x="0.722269" y="2.46642"> +<feDropShadow dx="-4.37114e-08" dy="1" flood-color="#050505" flood-opacity="1" in="SourceGraphic" result="Shadow" stdDeviation="1"/> +</filter> +</defs> +<g id="Layer-1" vectornator:layerName="Layer 1"> +<path d="M35.1413 3.47016L35.1416 6.93891L27.1415 6.93961L27.1464 63.3771C21.6096 65.0011 16.4869 67.0445 12.2717 69.6596C-9.35807 83.0789 12.5182 123.232 12.5264 123.222C12.5341 123.213 12.5498 123.2 12.5576 123.191C13.1518 124.609 14.5144 125.761 16.5579 126.534C18.7737 127.372 21.8204 127.855 26.8082 128.408C25.0644 132.318 24.4639 137.732 25.3409 144.846C26.0816 150.854 28.2327 155.309 32.5924 161.533C32.9615 162.06 34.1761 163.796 34.3427 164.033C35.013 164.984 35.5416 165.705 35.9991 166.408C38.1084 169.647 39.1319 172.149 39.3124 174.814C39.3701 175.665 39.3355 180.513 39.251 187.751C39.2362 189.024 39.0999 200.614 39.0959 200.907C37.7122 201.653 36.7523 203.069 36.7525 204.751C36.7527 207.19 38.7205 209.157 41.1591 209.157C43.5978 209.157 45.5653 207.189 45.5651 204.751C45.5649 203.019 44.5477 201.568 43.096 200.844C43.1023 200.341 43.2363 189.045 43.2511 187.782C43.3388 180.268 43.3816 175.583 43.3124 174.563C43.0775 171.096 41.7912 168.012 39.3427 164.251C38.854 163.5 38.3197 162.708 37.6237 161.72C37.4507 161.474 36.2342 159.766 35.8735 159.251C31.8564 153.517 29.9529 149.563 29.3096 144.346C28.2621 135.848 29.4895 130.5 32.027 127.533C32.6413 126.815 33.2789 126.306 33.9019 125.97C34.2334 125.792 34.4442 125.7 34.4956 125.689C35.5735 125.448 36.2365 124.392 35.9954 123.314C35.7543 122.236 34.6982 121.542 33.6202 121.783C33.2264 121.871 32.6655 122.078 31.9953 122.439C31.0669 122.939 30.1635 123.734 29.3079 124.658C23.0392 124.019 20.0922 123.577 17.9951 122.784C16.1482 122.086 15.9773 121.63 16.7137 120.347C25.511 115.923 43.9287 113.157 55.0885 113.156C63.8081 113.155 79.7952 116.873 90.4333 119.622C93.9977 120.542 94.7294 120.755 96.621 121.277C94.2649 123.034 89.4439 124.738 83.3087 125.247C83.0835 125.266 82.9434 125.443 82.7462 125.529C82.6074 125.348 82.5171 125.106 82.3711 124.935C81.4271 123.831 80.3863 122.999 79.3396 122.435C78.6693 122.074 78.1085 121.867 77.7145 121.779C76.6365 121.538 75.5805 122.233 75.3396 123.31C75.0987 124.388 75.7619 125.444 76.8399 125.685C76.8913 125.697 77.1021 125.788 77.4337 125.967C78.0567 126.302 78.6942 126.81 79.3088 127.529C81.8468 130.496 83.0751 135.844 82.029 144.341C81.3867 149.559 79.4839 153.513 75.4678 159.248C75.1071 159.763 73.8909 161.471 73.7179 161.717C73.0221 162.705 72.488 163.498 71.9994 164.248C69.5516 168.009 68.2658 171.094 68.0315 174.561C67.9625 175.581 68.0061 180.266 68.0951 187.78C68.1102 189.055 68.2793 200.673 68.2838 200.967C66.9421 201.725 66.0027 203.098 66.0028 204.749C66.0031 207.187 67.9709 209.155 70.4095 209.155C72.8481 209.154 74.8156 207.187 74.8154 204.748C74.8153 202.975 73.7598 201.482 72.2526 200.779C72.2461 200.277 72.1101 189.009 72.0952 187.748C72.0095 180.511 71.974 175.662 72.0316 174.811C72.2116 172.146 73.2659 169.644 75.3746 166.404C75.8321 165.701 76.3292 164.981 76.9994 164.029C77.1659 163.793 78.3803 162.056 78.7492 161.529C83.1079 155.305 85.2582 150.849 85.9979 144.841C86.8306 138.077 86.2654 132.917 84.7153 129.06C93.073 128.178 99.944 125.471 101.777 121.527C105.258 116.592 120.674 81.4184 100.46 69.1833C96.4145 66.7344 91.6503 64.7196 86.3971 63.1845L86.3922 6.93448L78.3609 6.93517L78.3606 3.46642L35.1413 3.47016ZM31.1419 10.9393L39.1107 10.9386L39.3309 27.9386C39.3406 28.6841 39.9605 29.2606 40.7061 29.2509C41.4516 29.2413 42.0593 28.6214 42.0497 27.8758L41.7983 10.9383L44.517 10.9381L44.7998 27.9381C44.8108 28.6836 45.3981 29.2615 46.1436 29.2505C46.8892 29.2394 47.4983 28.6209 47.4873 27.8753L47.2358 10.9379L49.9546 10.9376L50.2061 27.9376C50.2169 28.6831 50.8357 29.2608 51.5812 29.25C52.3267 29.2392 52.9044 28.6203 52.8936 27.8749L52.6421 10.9374L55.3609 10.9372L55.6124 27.9371C55.623 28.6827 56.242 29.2601 56.9875 29.2495C57.733 29.2389 58.3418 28.6199 58.3312 27.8744L58.0797 10.9369L60.8297 10.9367L61.05 27.9367C61.0546 28.3094 61.2147 28.6381 61.4563 28.8741C61.6979 29.1102 62.0211 29.2537 62.3938 29.2491C63.1394 29.2398 63.7468 28.6194 63.7375 27.8739L63.5173 10.9365L66.2986 10.9362L66.4875 27.905C66.4957 28.6505 67.1171 29.2568 67.8627 29.2486C68.6082 29.2404 69.1834 28.6503 69.1751 27.9047L68.9861 10.936L71.7674 10.9357L71.9564 27.9357C71.9605 28.3085 72.1132 28.6372 72.3627 28.8732C72.6122 29.1091 72.9587 29.2523 73.3315 29.2481C74.077 29.2398 74.6833 28.6498 74.6751 27.9042L74.4862 10.9355L82.3925 10.9348L82.3969 62.2473C78.1953 61.2363 73.8195 60.4151 69.2716 59.936L69.0841 59.936L69.0846 65.9672L62.3352 73.3116L62.211 82.5928L62.212 94.7803L56.5244 94.7808L50.3056 94.7814L50.2733 82.9376L50.1163 73.3126L43.1468 65.8132L43.1463 60.0945L31.1463 62.3455L31.1419 10.9393ZM46.0197 41.188L45.9904 64.3755L52.9599 71.8749L53.1492 91.9374L59.368 91.9368L59.5225 71.8743L66.2407 64.5612L66.2387 41.3425L46.0197 41.188ZM45.5578 121.282C45.0484 121.232 44.5158 121.369 44.0891 121.719C43.2356 122.421 43.1382 123.71 43.8394 124.563C43.8712 124.602 43.9181 124.67 44.0269 124.813C44.2163 125.062 44.4521 125.354 44.6832 125.688C45.3494 126.65 45.998 127.755 46.621 128.938C48.9603 133.379 50.0248 137.836 49.1222 141.875C48.4351 144.95 46.6061 147.711 43.4353 150.126C43.1568 150.338 42.1644 150.942 40.7792 151.751C40.6905 151.803 37.4839 153.662 37.4356 153.689C36.4728 154.23 36.1131 155.445 36.6545 156.408C37.1959 157.37 38.4106 157.699 39.3734 157.157C39.4251 157.128 42.7199 155.272 42.8108 155.22C44.3781 154.304 45.4057 153.669 45.8731 153.313C49.7829 150.336 52.1324 146.76 53.0285 142.75C54.1746 137.622 52.9238 132.266 50.1834 127.063C49.4811 125.729 48.7181 124.526 47.9643 123.438C47.5056 122.775 47.1244 122.271 46.9017 122C46.5511 121.574 46.0673 121.332 45.5578 121.282ZM65.7769 121.28C65.2675 121.33 64.7837 121.572 64.4331 121.999C64.2105 122.27 63.8293 122.774 63.3708 123.436C62.6172 124.525 61.8858 125.728 61.1835 127.062C58.444 132.265 57.163 137.621 58.3099 142.749C59.2067 146.759 61.5569 150.335 65.4671 153.311C65.9346 153.667 66.9935 154.302 68.5611 155.217C68.6519 155.27 71.9158 157.125 71.9675 157.154C72.9304 157.696 74.145 157.367 74.6862 156.404C75.2275 155.441 74.8676 154.227 73.9048 153.686C73.8565 153.658 70.6495 151.8 70.5608 151.748C69.1754 150.939 68.1829 150.336 67.9044 150.124C64.7332 147.71 62.9036 144.948 62.2161 141.874C61.3128 137.835 62.3766 133.378 64.715 128.936C65.3378 127.753 65.9862 126.649 66.6522 125.686C66.8833 125.352 67.1191 125.06 67.3084 124.811C67.4172 124.668 67.4953 124.6 67.5272 124.561C68.2282 123.708 68.0992 122.418 67.2457 121.717C66.8189 121.367 66.2862 121.23 65.7769 121.28ZM28.3735 160.064C27.8641 160.114 27.3492 160.357 26.9985 160.783C24.0604 164.358 22.5881 168.723 22.5621 174.253C22.5614 174.404 22.6311 200.839 22.627 200.877C21.1966 201.608 20.1896 203.036 20.1898 204.753C20.19 207.191 22.1578 209.159 24.5964 209.159C27.0351 209.158 29.0026 207.191 29.0024 204.752C29.0022 203.061 28.0235 201.65 26.627 200.908C26.6562 200.425 26.6538 197.055 26.6259 187.658C26.6229 186.662 26.5615 174.385 26.5622 174.252C26.584 169.618 27.7563 166.157 30.0925 163.314C30.7939 162.461 30.6644 161.203 29.811 160.502C29.3844 160.151 28.8829 160.015 28.3735 160.064ZM82.9679 160.06C82.4585 160.01 81.9571 160.147 81.5304 160.497C80.6772 161.199 80.5479 162.457 81.2494 163.31C83.5862 166.152 84.7591 169.613 84.7817 174.247C84.7823 174.378 84.7241 199.67 84.7527 200.841C83.3011 201.564 82.2529 203.015 82.253 204.747C82.2533 207.186 84.2524 209.153 86.691 209.153C89.1295 209.153 91.0971 207.185 91.0969 204.747C91.0967 203.049 90.1265 201.611 88.7215 200.872C88.7173 200.833 88.7318 194.753 88.7516 187.684C88.7544 186.688 88.7824 174.398 88.7817 174.247C88.7548 168.717 87.2817 164.353 84.343 160.778C83.9922 160.352 83.4774 160.109 82.9679 160.06Z" fill="url(#RadialGradient)" fill-rule="nonzero" filter="url(#Filter)" stroke="none" vectornator:shadowAngle="1.5708" vectornator:shadowColor="#050505" vectornator:shadowOffset="1" vectornator:shadowOpacity="1" vectornator:shadowRadius="2"/> +</g> +</svg> diff --git a/react-ui/docker/webserver/Dockerfile b/react-ui/docker/webserver/Dockerfile index 9c4f3f6765bed330e062267d329db9f2f5e04ce9..f4644ee1036b55d30673e0290d3a89aec8a68c60 100644 --- a/react-ui/docker/webserver/Dockerfile +++ b/react-ui/docker/webserver/Dockerfile @@ -1,17 +1,4 @@ -FROM node:alpine3.20 as builder - -COPY ./api/openapiv2/gosdn_northbound.swagger.json /app/api/openapiv2/gosdn_northbound.swagger.json -COPY ./react-ui /app/react-ui - -RUN cd /app/react-ui && \ - rm -rf node_modules && \ - rm yarn.lock && \ - yarn install --production && \ - yarn build - - -# webserver FROM nginx:alpine3.20 -COPY --from=builder /app/react-ui/dist /usr/share/nginx/html -COPY --from=builder /app/react-ui/docker/webserver/nginx.conf /etc/nginx/nginx.conf -EXPOSE 80 \ No newline at end of file + +COPY dist /usr/share/nginx/html +COPY docker/webserver/nginx.conf /etc/nginx/nginx.conf \ No newline at end of file diff --git a/react-ui/docker/webserver/nginx.conf b/react-ui/docker/webserver/nginx.conf index b9028bec32ba630f8277d054dcbfbac37d707297..87066f6ab89b1a840816f93fedc989e0d0736bfb 100644 --- a/react-ui/docker/webserver/nginx.conf +++ b/react-ui/docker/webserver/nginx.conf @@ -1,82 +1,52 @@ + +#user nobody; worker_processes 1; +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + + events { worker_connections 1024; - multi_accept on; - use epoll; } http { include mime.types; default_type application/octet-stream; - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main buffer=16k; - - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - server_tokens off; - - # Buffer size settings - client_body_buffer_size 10K; - client_header_buffer_size 8k; - client_max_body_size 8m; - large_client_header_buffers 4 8k; - - # File descriptor cache - open_file_cache max=2000 inactive=20s; - open_file_cache_valid 60s; - open_file_cache_min_uses 5; - open_file_cache_errors off; - - # Compression settings - gzip on; - gzip_comp_level 6; - gzip_min_length 256; - gzip_proxied any; - gzip_vary on; - gzip_types - application/javascript - application/json - application/x-javascript - application/xml - text/css - text/javascript - text/plain - text/xml - text/html - application/x-font-ttf - font/opentype - application/vnd.ms-fontobject - image/svg+xml; + #log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + # '$status $body_bytes_sent "$http_referer" ' + # '"$http_user_agent" "$http_x_forwarded_for"'; + + #access_log logs/access.log main; + + sendfile on; + #tcp_nopush on; + + #keepalive_timeout 0; + keepalive_timeout 65; resolver 127.0.0.11 ipv6=off; + #gzip on; + server { listen 80; server_name localhost; + #charset koi8-r; + + #access_log logs/host.access.log main; + + location ^~ /api/ { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - - # Proxy timeouts - proxy_connect_timeout 60s; - proxy_send_timeout 60s; - proxy_read_timeout 60s; - - # Proxy buffering - proxy_buffering on; - proxy_buffer_size 4k; - proxy_buffers 8 16k; # CORS headers add_header 'Access-Control-Allow-Origin' '*' always; @@ -105,23 +75,59 @@ http { try_files $uri $uri/ /index.html; } - - # Static asset handling with improved caching - location ~* \.(js|css|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|otf|eot)$ { + location ~* \.(js|css|jpg|png|svg|woff|woff2|ttf|otf|eot|ico)$ { root /usr/share/nginx/html; - expires 30d; - add_header Cache-Control "public, no-transform"; add_header 'Access-Control-Allow-Origin' '*' always; - - # Enable compression for these files - gzip_static on; # Serve pre-compressed files if available - - # Disable access logs for static files - access_log off; + expires 30d; + add_header Cache-Control "public"; } + + # #error_page 404 /404.html; + + # # redirect server error pages to the static page /50x.html + # # + # error_page 500 502 503 504 /50x.html; + # location = /50x.html { + # root html; + # } + + # proxy the PHP scripts to Apache listening on 127.0.0.1:80 + # + #location ~ \.php$ { + # proxy_pass http://127.0.0.1; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # root html; + # fastcgi_pass 127.0.0.1:9000; + # fastcgi_index index.php; + # fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} } + # another virtual host using mix of IP-, name-, and port-based configuration + # + #server { + # listen 8000; + # listen somename:8080; + # server_name somename alias another.alias; + + # location / { + # root html; + # index index.html index.htm; + # } + #} # HTTPS server @@ -144,4 +150,5 @@ http { # index index.html index.htm; # } #} + } \ No newline at end of file diff --git a/react-ui/index.html b/react-ui/index.html index 3dc374a596e8a0a21a920783305d30984626c95e..91d50a11c836668b61e6e60fee7dabe940186c0f 100755 --- a/react-ui/index.html +++ b/react-ui/index.html @@ -5,14 +5,13 @@ <link rel="icon" href="favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> - <meta name="author" content="Matthias Feyll" /> <meta name="description" - content="goSDN web ui" + content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="logo.png" /> <link rel="manifest" href="manifest.json" /> - <title>goSDN - ui</title> + <title>goSDN</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> diff --git a/react-ui/package.json b/react-ui/package.json index 1f742d78da361ba160c390cd6296d007c4539b42..709c5bfcb623cd76f6b0e5264378ad3f909a58a7 100755 --- a/react-ui/package.json +++ b/react-ui/package.json @@ -12,12 +12,8 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", - "@fullhuman/postcss-purgecss": "^7.0.2", "@reduxjs/toolkit": "^2.2.4", - "@types/react-grid-layout": "^1.3.5", - "@vitejs/plugin-react": "^4.2.1", "bootstrap": "^5.3.3", - "crypto-js": "^4.2.0", "dompurify": "^3.2.3", "i18next": "^24.0.5", "jwt-decode": "^4.0.0", @@ -25,43 +21,14 @@ "react-bootstrap": "^2.10.2", "react-dom": "^18.3.1", "react-error-boundary": "^4.1.2", - "react-grid-layout": "^1.5.0", "react-i18next": "^15.0.0", "react-redux": "^9.1.2", "react-router-dom": "^6.23.1", + "react-scripts": "5.0.1", "react-toastify": "^10.0.5", "redux": "^5.0.1", "redux-observable": "^3.0.0-rc.2", "redux-persist": "^6.0.0", - "sass": "1.82.0", - "sass-embedded": "^1.80.6", - "vite": "^6.0.3" - }, - "devDependencies": { - "@babel/runtime": "^7.21.5", - "@rtk-query/codegen-openapi": "^2.0.0", - "@testing-library/jest-dom": "^6.4.8", - "@testing-library/react": "^16.0.0", - "@testing-library/user-event": "^14.5.2", - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", - "@typescript-eslint/eslint-plugin": "^8.0.1", - "@typescript-eslint/parser": "^8.0.1", - "eslint": "^9.9.0", - "eslint-config-airbnb-typescript": "^18.0.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-import": "^2.27.5", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-prettier": "^5.2.1", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^5.1.0-rc.0", - "eslint-plugin-react-refresh": "^0.4.9", - "globals": "^15.9.0", - "prettier": "^3.3.3", - "react-scripts": "5.0.1", - "typescript": "^5.5.3", - "typescript-eslint": "^8.0.1", - "vite-bundle-visualizer": "^1.2.1", "web-vitals": "^4.2.2" }, "scripts": { @@ -70,9 +37,7 @@ "build::api": "npx @rtk-query/codegen-openapi ./scripts/openapi-config.json", "build": "yarn build::api && yarn build::frontend", "lint": "eslint src", - "lint::fix": "eslint src --fix", - "dev": "./scripts/dev.sh", - "postbuild": "purgecss --css dist/assets/*.css --content dist/index.html dist/assets/*.js --output dist/purged" + "lint::fix": "eslint src --fix" }, "eslintConfig": { "extends": [ @@ -90,5 +55,34 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@babel/runtime": "^7.21.5", + "@rtk-query/codegen-openapi": "^2.0.0", + "@testing-library/jest-dom": "^6.4.8", + "@testing-library/react": "^16.0.0", + "@testing-library/user-event": "^14.5.2", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^8.0.1", + "@typescript-eslint/parser": "^8.0.1", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^9.9.0", + "eslint-config-airbnb-typescript": "^18.0.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-prettier": "^5.2.1", + "eslint-plugin-react": "^7.32.2", + "eslint-plugin-react-hooks": "^5.1.0-rc.0", + "eslint-plugin-react-refresh": "^0.4.9", + "globals": "^15.9.0", + "prettier": "^3.3.3", + "sass": "1.82.0", + "sass-embedded": "^1.80.6", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.1", + "vite": "^6.0.3", + "vite-bundle-visualizer": "^1.2.1" } -} +} \ No newline at end of file diff --git a/react-ui/public/favicon.ico b/react-ui/public/favicon.ico old mode 100644 new mode 100755 index ae6dcf05241b89f446e88d15b81ffd69202fa83a..a11777cc471a4344702741ab1c8a588998b1311a Binary files a/react-ui/public/favicon.ico and b/react-ui/public/favicon.ico differ diff --git a/react-ui/public/fonts/inter-webfont.woff b/react-ui/public/fonts/inter-webfont.woff deleted file mode 100644 index 7eca6fe75d3ba56e14981e8f8e1c50d948bb8b56..0000000000000000000000000000000000000000 Binary files a/react-ui/public/fonts/inter-webfont.woff and /dev/null differ diff --git a/react-ui/public/fonts/inter-webfont.woff2 b/react-ui/public/fonts/inter-webfont.woff2 deleted file mode 100644 index 25ef870d53d5616ee3e92bbe3e85b5ed164fe4d2..0000000000000000000000000000000000000000 Binary files a/react-ui/public/fonts/inter-webfont.woff2 and /dev/null differ diff --git a/react-ui/scripts/build.sh b/react-ui/scripts/build.sh index fb6cb2a21737c17e269f4dbc7fd1b2befdd5e6ae..ad0f0cd0b20382110f1dbff59206f95c1d5a14f2 100755 --- a/react-ui/scripts/build.sh +++ b/react-ui/scripts/build.sh @@ -7,7 +7,7 @@ docker run --rm \ -w /app \ -u $(id -u):$(id -g) \ $IMAGE \ - yarn && yarn build + yarn install && yarn build if [ $? -ne 0 ]; then echo "Error while building frontend app" diff --git a/react-ui/scripts/dev.sh b/react-ui/scripts/dev.sh deleted file mode 100755 index 3a2d208858603433cf61c69ade5aa21d46f26377..0000000000000000000000000000000000000000 --- a/react-ui/scripts/dev.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env sh - -docker run \ - -it \ - --rm \ - -v $(pwd):/app \ - -w /app \ - -p 127.0.0.1:3000:3000 \ - --network gosdn-csbi-arista-base-net \ - node:20-alpine3.20 \ - npx vite diff --git a/react-ui/src/components/devices/reducer/device.reducer.ts b/react-ui/src/components/devices/reducer/device.reducer.ts index f211fe0249a5c423d3e90012fdc8cab7284ec6cc..8e4454bd285396b3aca4dce62bc7d1b1518fcd51 100755 --- a/react-ui/src/components/devices/reducer/device.reducer.ts +++ b/react-ui/src/components/devices/reducer/device.reducer.ts @@ -1,162 +1,107 @@ import { + api, NetworkelementFlattenedManagedNetworkElement, NetworkelementManagedNetworkElement, - PndPrincipalNetworkDomain + PndPrincipalNetworkDomain, + PndServiceGetPndListApiArg, } from '@api/api' import { DeviceViewTabValues } from '@component/devices/view/device.view.tabs' -import { createSlice, PayloadAction } from '@reduxjs/toolkit' -import { refreshUpdateTimer } from '@shared/reducer/routine.reducer' -import { Category, CategoryType } from '@shared/types/category.type' -import { REHYDRATE } from 'redux-persist' +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit' import { RootState } from 'src/stores' import '../routines/index' import { startListening } from '/src/stores/middleware/listener.middleware' export type Device = NetworkelementFlattenedManagedNetworkElement -interface SelectedObject { +interface SelectedDeviceInterface { device: Device mne: NetworkelementManagedNetworkElement | null json: JSON | null } +type SelectedDeviceType = SelectedDeviceInterface | null + export interface DeviceSliceState { devices: Device[] pnds: PndPrincipalNetworkDomain[] activeTab: DeviceViewTabValues - selected: SelectedObject | null + selectedDevice: SelectedDeviceType } const initialState: DeviceSliceState = { devices: [], pnds: [], activeTab: DeviceViewTabValues.METADATA, - selected: null, -} - -interface SetSelectedDeviceType { - device: Device | null, - options?: { - bypassCheck: boolean - } + selectedDevice: null, } const deviceSlice = createSlice({ name: 'device', initialState, reducers: { - setDevices: (state, action: PayloadAction<Device[] | undefined>) => { - state.devices = action.payload || [] + setDevices: (state, action: PayloadAction<Device[]>) => { + state.devices = action.payload }, - setPnds: (state, action: PayloadAction<PndPrincipalNetworkDomain[] | undefined>) => { - state.pnds = action.payload || [] + setPnds: (state, action: PayloadAction<PndPrincipalNetworkDomain[]>) => { + state.pnds = action.payload }, setActiveTab: (state, action: PayloadAction<DeviceViewTabValues>) => { state.activeTab = action.payload }, - setSelectedDevice: { - reducer: (state, { payload, meta }: PayloadAction<SetSelectedDeviceType, string, { skipListener?: boolean }>) => { - /** - * Do nothing if desired device is already selected - * Bypass the check if the flag is set to true. We - * use this bypass to trigger the listener functions - * accordingly - */ - if (!payload?.options?.bypassCheck && state.selected?.device.id === payload.device?.id) { - meta.skipListener = true - return - } - - if (!payload.device) { - throw Error('Passed null device as parameter while bypassing the safety check') - } - - let selectedObject: SelectedObject | null = null; - if (payload) { - selectedObject = { device: payload.device, mne: null, json: null } - } - - state.selected = selectedObject - }, - prepare: (payload) => { - return { - payload, - meta: { skipListener: false } - } + setSelectedDevice: (state, action: PayloadAction<Device | null>) => { + let selectedDevice: SelectedDeviceType = null + if (action.payload) { + selectedDevice = { device: action.payload, mne: null, json: null } } + + state.selectedDevice = selectedDevice }, setSelectedMne: (state, action: PayloadAction<NetworkelementManagedNetworkElement>) => { - if (!state.selected) { - throw new Error('Can not find corresponding device') - } - - // safety check to prevent possible race conditions - if (state.selected.device.id !== action.payload.id) { - // TODO proper error handling by retry fetching the device object - throw new Error('Device and mne id does not match') + if (!state.selectedDevice) { + throw new Error('Selected Device is null where it shouldn´t be null') } - state.selected.mne = action.payload + state.selectedDevice.mne = action.payload + // TODO maybe don´t take the device of "selected device" instead search in the devices array for the proper device + // should not make a diffrence due to pointer but dunno }, setSelectedJson: (state, action: PayloadAction<JSON>) => { - if (!state.selected) { + if (!state.selectedDevice) { throw new Error('Selected Device is null where it shouldn´t be null') } - state.selected.json = action.payload || null + state.selectedDevice.json = action.payload || null }, }, }) -export const { setDevices, setActiveTab, setSelectedDevice, setSelectedMne, setSelectedJson, setPnds } = +export const { setDevices, setActiveTab, setSelectedDevice, setSelectedMne, setSelectedJson } = deviceSlice.actions +const { setPnds } = deviceSlice.actions export default deviceSlice.reducer export const deviceReducerPath = deviceSlice.reducerPath -// add default selected device if no selected device is set -startListening({ - predicate: (action) => setDevices.match(action), - effect: async (action, listenerApi) => { - const { device: state } = listenerApi.getOriginalState() as RootState - if (state.selected) { - return - } - - // if there are no devices available do set null - const device = action.payload?.[0] || null - listenerApi.dispatch(setSelectedDevice({ device } as SetSelectedDeviceType)) - }, -}) +export const fetchPnds = createAsyncThunk('device/fetchPnds', (_, thunkApi) => { + const payload: PndServiceGetPndListApiArg = { + timestamp: new Date().getTime().toString(), + } -startListening({ - predicate: (action) => setSelectedMne.match(action), - effect: async (action, listenerApi) => { - listenerApi.dispatch(refreshUpdateTimer(Category.TAB as CategoryType)) - }, + const subscription = thunkApi.dispatch(api.endpoints.pndServiceGetPndList.initiate(payload)) + subscription.unwrap().then((response) => { + thunkApi.dispatch(setPnds(response.pnd)) + }) }) +// add default selected device if no selected device is set startListening({ predicate: (action) => setDevices.match(action), effect: async (action, listenerApi) => { - listenerApi.dispatch(refreshUpdateTimer(Category.DEVICE as CategoryType)) - }, -}) - -/** - * On startup reset the selected device - */ -startListening({ - predicate: ({ type }: any) => type === REHYDRATE, - effect: async (_, listenerApi) => { - const { device: state } = listenerApi.getState() as RootState - const device = state.selected?.device - if (!device) { - return + const { device } = listenerApi.getOriginalState() as RootState + if (!device.selectedDevice && !!action.payload[0]) { + listenerApi.dispatch(setSelectedDevice(action.payload[0])) } - - listenerApi.dispatch(setSelectedDevice({ device, options: { bypassCheck: true } } as SetSelectedDeviceType)) }, -}) \ No newline at end of file +}) diff --git a/react-ui/src/components/devices/routines/device.routine.ts b/react-ui/src/components/devices/routines/device.routine.ts index 058f65f9fb7416d1efb55cf7a00e70571b55fb30..10caffa2faa1eb0bdcba5f616c23f36c1721cb30 100755 --- a/react-ui/src/components/devices/routines/device.routine.ts +++ b/react-ui/src/components/devices/routines/device.routine.ts @@ -1,39 +1,26 @@ import { NetworkElementServiceGetAllFlattenedApiArg, api } from '@api/api' import { setDevices } from '@component/devices/reducer/device.reducer' import { createAsyncThunk } from '@reduxjs/toolkit' -import { addRoutine } from '@shared/reducer/routine.reducer' import { setUser } from '@shared/reducer/user.reducer' -import { Category, CategoryType } from '@shared/types/category.type' import { RootState } from 'src/stores' import { startListening } from '../../../stores/middleware/listener.middleware' export const FETCH_DEVICE_ACTION = 'subscription/device/fetchDevices' // continously fetch devices +const FETCH_DEVICES_INTERVAL = 15000 // in ms startListening({ actionCreator: setUser, effect: async (_, listenerApi) => { - listenerApi.dispatch( - addRoutine({ - thunk: fetchDevicesThunk, - category: Category.DEVICE as CategoryType, - payload: {}, - }) - ) + listenerApi.dispatch(fetchDevicesThunk()) }, }) -const FETCH_DEVICES_INTERVAL = 15000 // in ms export const fetchDevicesThunk = createAsyncThunk(FETCH_DEVICE_ACTION, (_, thunkApi) => { const { user } = thunkApi.getState() as RootState - if (!user.user?.roles) { - throw new Error('Background device fetching failed! User data is missing. Reload page or logout and login again') - // TODO - } - const payload: NetworkElementServiceGetAllFlattenedApiArg = { - pid: Object.keys(user.user.roles)[0], + pid: Object.keys(user?.user.roles)[0], timestamp: new Date().getTime().toString(), } diff --git a/react-ui/src/components/devices/routines/mne.routine.ts b/react-ui/src/components/devices/routines/mne.routine.ts index 2928693075a75bf7af1044d44a91488b56e10b23..7de1d18149c35c2bfbe47582d479a5736fa7f1b1 100755 --- a/react-ui/src/components/devices/routines/mne.routine.ts +++ b/react-ui/src/components/devices/routines/mne.routine.ts @@ -6,42 +6,26 @@ import { setSelectedMne, } from '@component/devices/reducer/device.reducer' import { createAsyncThunk } from '@reduxjs/toolkit' -import { addRoutine } from '@shared/reducer/routine.reducer' -import { Category, CategoryType } from '@shared/types/category.type' +import { addRoutine, CATEGORIES } from '@shared/reducer/routine.reducer' import { RootState } from 'src/stores' import { startListening } from '../../../stores/middleware/listener.middleware' export const FETCH_MNE_ACTION = 'subscription/device/fetchSelectedMNE' - -/** - * #0 - * Trigger fetch MNE (#1) - * - * Triggered by a selectedDevice - */ +// fetch mne if selected device is set startListening({ - predicate: (action) => setSelectedDevice.match(action) && !!action.payload.device && !action.meta?.skipListener, + predicate: (action) => setSelectedDevice.match(action) && !!action.payload, effect: async (action, listenerApi) => { - - const device = action.payload.device - listenerApi.dispatch( addRoutine({ thunk: fetchSelectedMneThunk, - category: Category.TAB as CategoryType, - payload: device, + category: CATEGORIES.TAB, + payload: action.payload, }) ) }, }) -/** - * #1 - * Fetch MNE - * - * Triggered by #0 - */ const FETCH_MNE_INTERVAL = 5000 // in ms export const fetchSelectedMneThunk = createAsyncThunk( FETCH_MNE_ACTION, @@ -72,12 +56,7 @@ export const fetchSelectedMneThunk = createAsyncThunk( } ) -/** - * #2 - * Received MNE - * - * Triggered by #1 - */ +// save fetched mne startListening({ predicate: (action) => api.endpoints.networkElementServiceGet.matchFulfilled(action), effect: async (action, listenerApi) => { @@ -85,12 +64,7 @@ startListening({ }, }) -/** - * #3 - * Fetch & receive json - * - * Triggered by #2 - */ +// save fetched mne startListening({ predicate: (action) => setSelectedMne.match(action), effect: async (action, listenerApi) => { diff --git a/react-ui/src/components/devices/view/device.scss b/react-ui/src/components/devices/view/device.scss index 8d4099fc88cacccd6dd4b91d45db664668c5260c..540cd4d01d67ba3e11b5e8d18c654dceb160ee95 100755 --- a/react-ui/src/components/devices/view/device.scss +++ b/react-ui/src/components/devices/view/device.scss @@ -24,6 +24,11 @@ } } +.c-box { + padding: 2em !important; + padding-top: 1em !important; +} + .border-right { $border-padding: 2em; @@ -52,6 +57,5 @@ &.active { color: map-get($theme-colors, primary); font-weight: 500; - text-decoration: underline; } } diff --git a/react-ui/src/components/devices/view/device.view.table.tsx b/react-ui/src/components/devices/view/device.view.table.tsx index 1f7221eadb2923a4a70fe81dd156efa4fc49175b..312caab60d858b0931e50154c2c6e796e0405e47 100755 --- a/react-ui/src/components/devices/view/device.view.table.tsx +++ b/react-ui/src/components/devices/view/device.view.table.tsx @@ -1,66 +1,54 @@ -import { insertMarkTags } from "@helper/text"; import { useAppSelector } from "@hooks"; -import DOMPurify from 'dompurify'; -import { MutableRefObject, useCallback, useRef } from "react"; +import { MutableRefObject, useCallback } from "react"; import { OverlayTrigger, Table, Tooltip } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { useDeviceTableViewModel } from "../view_model/device.table.viewmodel"; -const cropUUID = (uuid: string): string => { - return uuid.substring(0, 3) + "..." + uuid.substring(uuid.length - 3, uuid.length); -} - export const DeviceViewTable = (searchRef: MutableRefObject<HTMLInputElement>) => { - const { devices, pnds, selected: selectedDevice } = useAppSelector(state => state.device); + const { devices, pnds, selectedDevice } = useAppSelector(state => state.device); const { t } = useTranslation('common'); - const tableRef = useRef(); - const { trClickHandler } = useDeviceTableViewModel(searchRef, tableRef); + const { trClickHandler } = useDeviceTableViewModel(searchRef); - const getDeviceTable = useCallback(() => { - const search = searchRef.current?.value; - let filtered = devices - // filter if something is in search - if (search) { - filtered = devices.filter((device) => { - const user = pnds.find(pnd => pnd.id === device.pid); + const cropUUID = (uuid: string): string => { + return uuid.substring(0, 3) + "..." + uuid.substring(uuid.length - 3, uuid.length); + } - return device.id?.includes(search) || - device.name?.includes(search) || - user?.name?.includes(search); - }) - } + const getDeviceTable = useCallback(() => { + return devices.filter((device) => { + if (!searchRef.current?.value) { + return true; + } - return filtered.map((device, index) => { + const searchInput = searchRef.current!.value; const user = pnds.find(pnd => pnd.id === device.pid); - const username = user?.name || '' - const deviceId = device.id!; - const cropedId = cropUUID(deviceId) - const devicename = device.name || ''; - - const rowData = username + ";" + deviceId + ";" + devicename + return device.id.includes(searchInput) || device.name.includes(searchInput) || user?.name.includes(searchInput); + }).map((device, index) => { + const user = pnds.find(pnd => pnd.id === device.pid); return ( - <tr data-copy-value={rowData} key={index} onClick={() => trClickHandler(device)} className={selectedDevice?.device.id === deviceId ? 'active' : ''}> - <td data-copy-value={devicename} dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(devicename, search) : DOMPurify.sanitize(devicename) }}></td> - <OverlayTrigger overlay={<Tooltip id={device.id}>{deviceId}</Tooltip>}> - <td data-copy-value={deviceId} dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(cropedId, search) : DOMPurify.sanitize(cropedId) }}></td> + <tr key={index} onClick={() => trClickHandler(device)} className={selectedDevice?.device.id === device.id ? 'active' : ''}> + <td>{device.name}</td> + <OverlayTrigger overlay={<Tooltip id={device.id}>{device.id}</Tooltip>}> + <td>{cropUUID(device.id)}</td> </OverlayTrigger> - <td data-copy-value={username} dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(username, search) : DOMPurify.sanitize(username) }}></td> + <td>{user?.name || ''}</td> + <td></td> </tr> - ) }) }, [devices, searchRef, pnds, selectedDevice, trClickHandler]); + return ( - <Table striped responsive className="device-table" ref={tableRef}> + <Table striped responsive className="device-table"> <thead> <tr> <th>{t('device.table.header.name')}</th> <th>{t('device.table.header.uuid')}</th> <th>{t('device.table.header.user')}</th> + <th>{t('device.table.header.last_updated')}</th> </tr> </thead> <tbody> diff --git a/react-ui/src/components/devices/view/device.view.tabs.tsx b/react-ui/src/components/devices/view/device.view.tabs.tsx index ef8ba120fb4e66dfabaf5b3d81beecee5445c0bd..2929f9b64855359fa19ea9c0e17d90f5af79bb61 100755 --- a/react-ui/src/components/devices/view/device.view.tabs.tsx +++ b/react-ui/src/components/devices/view/device.view.tabs.tsx @@ -8,7 +8,7 @@ export enum DeviceViewTabValues { } export const DeviceViewTabs = (activeTab: DeviceViewTabValues) => { - const { selected: selectedDevice } = useAppSelector(state => state.device); + const { selectedDevice } = useAppSelector(state => state.device); const { jsonYang } = useDeviceTabsViewModel(); const metadataTab = () => { @@ -24,6 +24,8 @@ export const DeviceViewTabs = (activeTab: DeviceViewTabValues) => { <> {jsonYang && <JsonViewer json={jsonYang} /> + + //<ReactJson src={selectedDevice.json} name={false} collapsed={true} quotesOnKeys={false} /> } </> ); diff --git a/react-ui/src/components/devices/view/device.view.tsx b/react-ui/src/components/devices/view/device.view.tsx index 6bd702bf7d895bf58e679c380edb7e5ebb384a8e..2602463bba8e51a8be7aff14b50008d0c9ad32ce 100755 --- a/react-ui/src/components/devices/view/device.view.tsx +++ b/react-ui/src/components/devices/view/device.view.tsx @@ -1,44 +1,33 @@ -import { faGripVertical } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { GridLayout } from '@layout/grid.layout/grid.layout'; -import UpdateIndicator from '@layout/grid.layout/update-inidicator.layout/update-indicator.layout'; -import { Category, CategoryType } from '@shared/types/category.type'; import { useRef } from 'react'; import { Button, Col, Container, Form, Nav, NavLink, Row } from 'react-bootstrap'; import { useTranslation } from 'react-i18next'; import { useDeviceViewModel } from '../view_model/device.viewmodel'; import './device.scss'; import { DeviceViewTable } from './device.view.table'; -import { DeviceViewTabValues, DeviceViewTabs } from './device.view.tabs'; +import { DeviceViewTabs, DeviceViewTabValues } from './device.view.tabs'; -const DeviceView = () => { +function DeviceView() { const { t } = useTranslation('common'); const searchRef = useRef<HTMLInputElement>(null); const { activeTab, setActiveTab, handleActiveTabLink } = useDeviceViewModel(); return ( <div className='m-4 pt-4'> - <GridLayout> - <> - <div key="device-list"> - <Container className='c-box hoverable h-100'> - <UpdateIndicator - category={Category.DEVICE as CategoryType} - updateInterval={15000} - /> - <FontAwesomeIcon icon={faGripVertical} className="drag-handle" /> + <Container fluid> + <Row> + <Col sm={5}> + <Container className='bg-white rounded c-box'> <Row> - <Col sm={12} className='mt-4'> - <h3 className='text-black-50'>{t('device.title')}</h3> - </Col> + <Col sm={12} className='mt-4'><h3 className='text-black-50'>{t('device.title')}</h3></Col> </Row> + <Row className='align-items-center'> - <Col xs={12} sm={6}> + <Col sm={6}> <Form.Group controlId='device.search' className='p-0 mx-1 pt-2'> <Form.Control type="text" placeholder={t('device.search.placeholder')} ref={searchRef} /> </Form.Group> </Col> - <Col xs={12} sm={6} className='pt-2'> + <Col sm={{ span: 3, offset: 3 }} className='pt-2'> <Button variant='primary' className='w-100 my-auto'>{t('device.add_device_button')}</Button> </Col> </Row> @@ -49,44 +38,29 @@ const DeviceView = () => { </Col> </Row> </Container> - </div> - - <div key="device-details"> - <Container className='c-box hoverable h-100'> - <UpdateIndicator - category={Category.TAB as CategoryType} - updateInterval={5000} - /> - <FontAwesomeIcon icon={faGripVertical} className="drag-handle" /> + </Col> + <Col sm={7}> + <Container className='bg-white rounded c-box'> <Row> - <Col xs={12} className='mt-4'> + <Col sm={12} className='mt-4'> <Nav className='justify-content-around'> - <NavLink - className={handleActiveTabLink(DeviceViewTabValues.METADATA) + " tab-links"} - onClick={() => setActiveTab(DeviceViewTabValues.METADATA)} - > - {t('device.tabs.metadata.title')} - </NavLink> - <NavLink - className={handleActiveTabLink(DeviceViewTabValues.YANGMODEL) + " tab-links"} - onClick={() => setActiveTab(DeviceViewTabValues.YANGMODEL)} - > - {t('device.tabs.yang_model.title')} - </NavLink> + <NavLink className={handleActiveTabLink(DeviceViewTabValues.METADATA) + " tab-links"} onClick={() => setActiveTab(DeviceViewTabValues.METADATA)}>{t('device.tabs.metadata.title')}</NavLink> + <NavLink className={handleActiveTabLink(DeviceViewTabValues.YANGMODEL) + " tab-links"} onClick={() => setActiveTab(DeviceViewTabValues.YANGMODEL)}>{t('device.tabs.yang_model.title')}</NavLink> </Nav> </Col> </Row> + <Row className='align-items-start'> - <Col xs={12}> + <Col sm={12} className='pt-2'> {DeviceViewTabs(activeTab)} </Col> </Row> </Container> - </div> - </> - </GridLayout> + </Col> + </Row> + </Container> </div> - ); -}; + ) +} -export default DeviceView; \ No newline at end of file +export default DeviceView diff --git a/react-ui/src/components/devices/view_model/device.table.viewmodel.ts b/react-ui/src/components/devices/view_model/device.table.viewmodel.ts index 4b328d63955f3827a3f6cdabbf83951505fe7dfa..df75953288d659c17a53a744c7c4ba015f0dcab9 100755 --- a/react-ui/src/components/devices/view_model/device.table.viewmodel.ts +++ b/react-ui/src/components/devices/view_model/device.table.viewmodel.ts @@ -1,100 +1,32 @@ import { Device, setSelectedDevice } from "@component/devices/reducer/device.reducer"; -import { faCopy } from "@fortawesome/free-solid-svg-icons"; import { useAppDispatch } from "@hooks"; -import { useMenu } from "@provider/menu/menu.provider"; -import { useUtils } from "@provider/utils.provider"; import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { toast } from "react-toastify"; -export const useDeviceTableViewModel = (searchRef, tableRef) => { +export const useDeviceTableViewModel = (searchRef) => { const [searchTerm, setSearchTerm] = useState(''); const dispatch = useAppDispatch(); - const { subscribe } = useMenu(); - const { toClipboard } = useUtils(); - const { t } = useTranslation('common'); - - - const registerMenuOptions = () => { - const subscription = subscribe({ - target: tableRef.current, - actions: [ - { - key: t('device.table.actions.copy'), - icon: faCopy, - action: (clickedElement) => { - if (clickedElement) { - const text = clickedElement.dataset.copyValue - if (!text) { - toast.warn(t('global.toast.copied_failed')) - return - } - - - toClipboard(text) - } - } - }, - - { - key: t('device.table.actions.copy_row'), - icon: faCopy, - action: (clickedElement) => { - let parent = clickedElement; - while (parent && parent.tagName !== 'TR') { - parent = parent.parentNode; - } - - const text = parent.dataset.copyValue - if (!text) { - toast.warn(t('global.toast.copied_failed')) - return - } - toClipboard(text) - } - } - ] - }) - - return () => { - subscription.unsubscribe() - } - } - - // seperate use effect to rerun this after tableref and subscribe are initialized - useEffect(() => { - if (!subscribe || !tableRef.current) { - return - } - - const unsubscribe = registerMenuOptions() - - return () => { - unsubscribe() - } - }, [tableRef, subscribe]) useEffect(() => { - if (!searchRef.current) { - return - } - const handleSearchChange = () => { - setSearchTerm(searchRef.current.value); + if (searchRef.current) { + setSearchTerm(searchRef.current.value); + } }; - searchRef.current.addEventListener('input', handleSearchChange); + if (searchRef.current) { + searchRef.current.addEventListener('input', handleSearchChange); + } return () => { if (searchRef.current) { searchRef.current.removeEventListener('input', handleSearchChange); } }; - }, [searchRef]); + }, []); const trClickHandler = (device: Device) => { - dispatch(setSelectedDevice({ device })); + dispatch(setSelectedDevice(device)); } diff --git a/react-ui/src/components/devices/view_model/device.tabs.viewmodel.ts b/react-ui/src/components/devices/view_model/device.tabs.viewmodel.ts index af4cc3abb9f0b2e207acd5c71fbb48f4a0562ca7..4a60567b68e78e571577d7f74fb0729df47008cb 100755 --- a/react-ui/src/components/devices/view_model/device.tabs.viewmodel.ts +++ b/react-ui/src/components/devices/view_model/device.tabs.viewmodel.ts @@ -7,7 +7,7 @@ export enum DeviceViewTabValues { } export const useDeviceTabsViewModel = () => { - const { selected: selectedDevice } = useAppSelector((state) => state.device) + const { selectedDevice } = useAppSelector((state) => state.device) const getYangModelJSON = (): JSON | null => { if (!selectedDevice?.json) { diff --git a/react-ui/src/components/devices/view_model/device.viewmodel.ts b/react-ui/src/components/devices/view_model/device.viewmodel.ts index 1cce2d59a2e984721d4730b9e98c310191a70937..9a0fbe17a0b8f3bf0aa5c00e8ef10a9d56b4a08d 100755 --- a/react-ui/src/components/devices/view_model/device.viewmodel.ts +++ b/react-ui/src/components/devices/view_model/device.viewmodel.ts @@ -7,7 +7,7 @@ export const useDeviceViewModel = () => { const { activeTab } = useAppSelector((state) => state.device) const dispatch = useAppDispatch() - useEffect(() => { }, []) + useEffect(() => {}, []) const handleActiveTabLink = (tabLink: DeviceViewTabValues) => { return activeTab === tabLink ? 'active' : '' diff --git a/react-ui/src/components/login/layouts/login.layout.tsx b/react-ui/src/components/login/layouts/login.layout.tsx index 58e8fc99133d8674b57c7d39f06e40da2c9f03e4..556dc6de436397b283bd426041b1d08749cf505a 100755 --- a/react-ui/src/components/login/layouts/login.layout.tsx +++ b/react-ui/src/components/login/layouts/login.layout.tsx @@ -20,6 +20,4 @@ export const LoginLayout = () => { return ( <LoginView>{outlet}</LoginView> ) -} - -export default LoginLayout \ No newline at end of file +} \ No newline at end of file diff --git a/react-ui/src/components/login/view/login.view.tsx b/react-ui/src/components/login/view/login.view.tsx index 62c84eeb8b07dae2c3e2aa180d36a14d8761e18f..03d7406f42bc37a78f9e6047f4aa70758e8b8258 100755 --- a/react-ui/src/components/login/view/login.view.tsx +++ b/react-ui/src/components/login/view/login.view.tsx @@ -1,10 +1,10 @@ -import { BasicProp } from '@shared/types/interfaces.type' +import logo from '@assets/logo.svg' +import { BasicProp } from '@helper/interfaces' import React, { useRef } from 'react' import { Alert, Button, Col, Container, Form, Image, Row, Spinner } from 'react-bootstrap' import { useTranslation } from 'react-i18next' import useLoginViewModel from '../viewmodel/login.viewmodel' import './login.scss' -import logo from '/public/logo.svg' const LoginView: React.FC<BasicProp> = () => { const { t } = useTranslation('common') diff --git a/react-ui/src/components/login/viewmodel/login.viewmodel.ts b/react-ui/src/components/login/viewmodel/login.viewmodel.ts index fabb0a8613cfee92f864beef15a998dda159238f..60c39b55ab7efe77b90fe5c449504f1d1193e2e6 100755 --- a/react-ui/src/components/login/viewmodel/login.viewmodel.ts +++ b/react-ui/src/components/login/viewmodel/login.viewmodel.ts @@ -7,9 +7,9 @@ export interface PageLoginState { } export default function useLoginViewModel() { - const { login, loginProperties } = useAuth(); - const { isLoading: loginLoading, error: loginError, reset: resetLogin } = loginProperties; - + const {login, loginProperties} = useAuth(); + const {isLoading: loginLoading, error: loginError, reset: resetLogin} = loginProperties!; + const [localFormState, updateLocalFormState] = useState({ submitted: false, valid: false, diff --git a/react-ui/src/i18n/locales/en/translations.json b/react-ui/src/i18n/locales/en/translations.json index 53444b9e0d6745f364a559342164dca89d7abc0f..46b76563c7d78355e9484e49d344f4132bf3766d 100755 --- a/react-ui/src/i18n/locales/en/translations.json +++ b/react-ui/src/i18n/locales/en/translations.json @@ -6,8 +6,7 @@ "empty_field": "This field can´t be empty" }, "toast": { - "copied": "Copied to clipboard", - "copied_failed": "Copying to clipboard failed" + "copied": "Copied to clipboard" }, "menu_item": { "logout": "Logout" @@ -36,10 +35,6 @@ "uuid": "UUID", "user": "User", "last_updated": "Last updated" - }, - "actions": { - "copy": "Copy", - "copy_row": "Copy row" } }, "search": { @@ -53,9 +48,6 @@ "yang_model": { "title": "YANG Model" } - }, - "box": { - "lastUpdate": "Last updated {{seconds}} seconds ago" } }, "protected": { diff --git a/react-ui/src/index.scss b/react-ui/src/index.scss index 5c9f184b7fc394991e9895411be67b1f31f36722..8dd280e6436cf6df71772494e87cdd950872f16b 100755 --- a/react-ui/src/index.scss +++ b/react-ui/src/index.scss @@ -1,4 +1,4 @@ -@import "./shared/style/index.scss"; +@import './shared/style/index.scss'; body { margin: 0; diff --git a/react-ui/src/index.tsx b/react-ui/src/index.tsx index 3697efd07379da0126d042b824b9c3d857bb6fed..8383248cea22fc89c4ba1c9f5248458f7fa150ec 100755 --- a/react-ui/src/index.tsx +++ b/react-ui/src/index.tsx @@ -16,7 +16,6 @@ import { router } from './routes' import './shared/icons/icons' import { persistor, store } from './stores' -window.env = window.location.hostname === 'localhost' ? 'development' : 'production'; ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> diff --git a/react-ui/src/routes.tsx b/react-ui/src/routes.tsx index a476feaaedfd8922a1841bd6f877792416856f13..368df55a472bdbc20bc2dd34bb9fb1f33b43047f 100755 --- a/react-ui/src/routes.tsx +++ b/react-ui/src/routes.tsx @@ -1,50 +1,21 @@ import { BasicLayout } from "@layout/basic.layout"; import { ProtectedLayout } from "@layout/protected.layout/protected.layout"; -import DelayedRender, { SplashScreen } from "@utils/loading-fallback"; -import { lazy, Suspense } from 'react'; import { createBrowserRouter, createRoutesFromElements, Navigate, Route } from "react-router-dom"; +import DeviceView from "./components/devices/view/device.view"; +import { LoginLayout } from "./components/login/layouts/login.layout"; export const DEVICE_URL = '/device/'; export const LOGIN_URL = '/login'; -// Lazy load components -const DeviceView = lazy(() => import('./components/devices/view/device.view')); -const LoginLayout = lazy(() => import('./components/login/layouts/login.layout')); export const router = createBrowserRouter( createRoutesFromElements( <Route element={<BasicLayout />}> - <Route - path={LOGIN_URL} - element={ - <Suspense fallback={null}> - <DelayedRender> - <LoginLayout /> - </DelayedRender> - </Suspense> - } - /> + <Route path={LOGIN_URL} element={<LoginLayout />} /> <Route element={<ProtectedLayout />}> - <Route - path={DEVICE_URL} - element={ - <DelayedRender - loading={{ - minimumLoadingTime: 1000, - component: SplashScreen - }} - > - <Suspense fallback={null}> - <DeviceView /> - </Suspense> - </DelayedRender> - } - /> - <Route - path="/" - element={<Navigate to={DEVICE_URL} replace={true} />} - /> + <Route path={DEVICE_URL} element={<DeviceView />} /> + <Route path="/" element={<Navigate to={DEVICE_URL} replace={true} />} /> </Route> - </Route > + </Route> ) -); \ No newline at end of file +) \ No newline at end of file diff --git a/react-ui/src/shared/components/json_viewer/view/json_viewer.view.tsx b/react-ui/src/shared/components/json_viewer/view/json_viewer.view.tsx index 33e52a39788fe1120c18972c14181a6abaf8067b..720f75c7d26b4ffd181707c46ff42502168a72c0 100755 --- a/react-ui/src/shared/components/json_viewer/view/json_viewer.view.tsx +++ b/react-ui/src/shared/components/json_viewer/view/json_viewer.view.tsx @@ -1,6 +1,5 @@ import { faAlignRight, faPenToSquare, faTrashCan } from "@fortawesome/free-solid-svg-icons" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" -import { insertMarkTags } from "@helper/text" import DOMPurify from 'dompurify' import React, { Suspense, useMemo, useRef } from "react" import { Form, Table } from "react-bootstrap" @@ -31,6 +30,13 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => { ) }, [breadcrumbs]) + const insertMarkTags = (text: string, search: string): string => { + const start = text.indexOf(search) + const end = start + search.length + + return DOMPurify.sanitize(text.substring(0, start)) + "<span class='highlight'>" + DOMPurify.sanitize(search) + "</span>" + DOMPurify.sanitize(text.substring(end, text.length)) + } + const renderInner = (innerJson: JSON, nested: number = 0, parentKey: string = "", path: string = "/network-instance/0/"): JSX.Element => { path += parentKey + (parentKey === "" ? "" : "/") @@ -124,7 +130,7 @@ export const JsonViewer = ({ json }: JsonViewerProbs) => { const searchHTML = () => { return ( <> - <Form.Group controlId='json_viewer.search' className='p-0 mx-1 pt-2'> + <Form.Group controlId='device.search' className='p-0 mx-1 pt-2'> <Form.Control type="text" placeholder={t('device.search.placeholder')} ref={search} /> </Form.Group> </> diff --git a/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx b/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx index f0bc922126f6752e9fcb0182391bd3deea461bb7..6c3ac78f306d0ff0b1c09263a4d791795757b51c 100644 --- a/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx +++ b/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx @@ -67,31 +67,29 @@ export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelTy } const registerMenuOptions = () => { - if (!container.current) { - return () => { } - } - - const subscription = subscribe({ - target: container.current, - actions: [ - { - key: t('json_viewer.copy'), - icon: faCopy, - action: (clickedElement) => { - let parent = clickedElement; - while (parent && parent.tagName !== 'TR') { - parent = parent.parentNode; + if (container.current) { + const subscription = subscribe({ + target: container.current, + actions: [ + { + key: t('json_viewer.copy'), + icon: faCopy, + action: (clickedElement) => { + let parent = clickedElement; + while (parent && parent.tagName !== 'TR') { + parent = parent.parentNode; + } + + const copyValue = parent.dataset.copyValue + toClipboard(copyValue) } - - const copyValue = parent.dataset.copyValue - toClipboard(copyValue) } - } - ] - }) + ] + }) - return () => { - subscription.unsubscribe(); + return () => { + subscription.unsubscribe(); + } } } @@ -139,7 +137,7 @@ export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelTy }, [searchTerm]) useEffect(() => { - const unsubscribe = registerMenuOptions(); + registerMenuOptions(); if (search.current) { search.current.addEventListener('input', handleSearchInput) @@ -149,7 +147,6 @@ export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelTy if (search.current) { search.current.removeEventListener('input', handleSearchInput) } - unsubscribe() } }, []) diff --git a/react-ui/src/shared/helper/debug.ts b/react-ui/src/shared/helper/debug.ts deleted file mode 100644 index db10a0979ac9b5acfab21af71fc4c1dc2595fbe3..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/helper/debug.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const warnMessage = (message: string) => { - if (window?.env === 'development') { - console.warn("Debug: \n" + message) - } -} - -export const infoMessage = (message: string) => { - if (window?.env === 'development') { - console.info("Info: \n" + message) - } -} \ No newline at end of file diff --git a/react-ui/src/shared/types/interfaces.type.ts b/react-ui/src/shared/helper/interfaces.ts similarity index 100% rename from react-ui/src/shared/types/interfaces.type.ts rename to react-ui/src/shared/helper/interfaces.ts diff --git a/react-ui/src/shared/helper/text.ts b/react-ui/src/shared/helper/text.ts deleted file mode 100644 index 6aee13790561eefcfe347fb2e42a79c5ef84cfec..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/helper/text.ts +++ /dev/null @@ -1,12 +0,0 @@ - -import DOMPurify from 'dompurify' - -export const insertMarkTags = (text: string, search: string): string => { - const start = text.indexOf(search) - if (start === -1) { - return DOMPurify.sanitize(text) - } - const end = start + search.length - - return DOMPurify.sanitize(text.substring(0, start)) + "<span class='highlight'>" + DOMPurify.sanitize(search) + "</span>" + DOMPurify.sanitize(text.substring(end, text.length)) -} \ No newline at end of file diff --git a/react-ui/src/shared/icons/icons.ts b/react-ui/src/shared/icons/icons.ts index 9c8791cc90cdd443a18fa06648ed2802b9a28b03..00021aa116faae88600fd06ab461aabd41aee9bb 100755 --- a/react-ui/src/shared/icons/icons.ts +++ b/react-ui/src/shared/icons/icons.ts @@ -1,4 +1,4 @@ import { library } from '@fortawesome/fontawesome-svg-core' -import { faSpinner } from '@fortawesome/free-solid-svg-icons' +import { faSpinner, fas } from '@fortawesome/free-solid-svg-icons' -library.add(faSpinner) \ No newline at end of file +library.add(fas, faSpinner) \ No newline at end of file diff --git a/react-ui/src/shared/layouts/grid.layout/grid.layout.scss b/react-ui/src/shared/layouts/grid.layout/grid.layout.scss deleted file mode 100644 index c90375d371f5e78827676f9520f03b1b388a5cc4..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/layouts/grid.layout/grid.layout.scss +++ /dev/null @@ -1,81 +0,0 @@ -@import "/src/shared/style/colors.scss"; - -.drag-handle { - position: absolute; - top: 0; - right: 0; - padding: 10px; - cursor: grab; - color: map-get($theme-colors, "dark"); - background-color: lighten(map-get($theme-colors, primary), 38%); - border-radius: 0 0.25rem 0 0.25rem; - border-left: 1px solid lighten(map-get($theme-colors, dark), 35%); - border-bottom: 1px solid lighten(map-get($theme-colors, dark), 35%); - z-index: 10; - - &:hover { - color: map-get($theme-colors, primary); - background-color: lighten(map-get($theme-colors, primary), 35%); - } - - &:active { - cursor: grabbing; - } -} - -.react-grid-item { - min-height: 600px !important; - - &.react-draggable-dragging { - z-index: 100; - - .drag-handle { - cursor: grabbing; - } - } -} - -.react-grid-layout { - width: 100% !important; -} - -.react-grid-item.react-grid-placeholder { - background: lighten(map-get($theme-colors, primary), 30%) !important; - opacity: 0.2; - transition-duration: 100ms; - z-index: 2; - border-radius: 4px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; -} - -.react-grid-item { - /* Hide resize handle by default */ - .react-resizable-handle-se { - opacity: 0; - transition: opacity 0.2s ease-in-out; - } - - /* Show resize handle on container hover */ - &:hover .react-resizable-handle-se { - opacity: 1; - } -} - -/* Style the resize handle */ -.react-resizable-handle-se { - position: absolute; - right: 0; - bottom: 0; - width: 20px; - height: 20px; - background-image: url("data:image/svg+xml;charset=utf-8;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2IDYiPjxwYXRoIGQ9Ik02IDZIMHYtNmg2djZ6TTUgMUgxdjRoNFYxeiIgZmlsbD0iIzk5OTk5OSIvPjwvc3ZnPg=="); - background-position: bottom right; - background-repeat: no-repeat; - background-size: 10px 10px; - cursor: se-resize; - z-index: 10; -} diff --git a/react-ui/src/shared/layouts/grid.layout/grid.layout.tsx b/react-ui/src/shared/layouts/grid.layout/grid.layout.tsx deleted file mode 100644 index c184e655b3ba7fa74d951cda2cd6a4ec421691d6..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/layouts/grid.layout/grid.layout.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { ReactElement, useEffect, useState } from 'react'; -import { Responsive, WidthProvider } from 'react-grid-layout'; -import 'react-grid-layout/css/styles.css'; -import 'react-resizable/css/styles.css'; -import './grid.layout.scss'; - -const ResponsiveGridLayout = WidthProvider(Responsive); - -interface GridLayoutProps { - children: ReactElement; -} - -export const GridLayout: React.FC<GridLayoutProps> = ({ children }) => { - const rowHeight = 50; - const [mounted, setMounted] = useState(false); - const layouts = { - lg: [ - { i: 'device-list', x: 0, y: 0, w: 1, h: 1, minW: 1, minH: 1 }, - { i: 'device-details', x: 2, y: 0, w: 2, h: 1, minW: 2, minH: 1 } - ] - }; - - useEffect(() => { - setMounted(true); - // Force layout recalculation after mount - window.dispatchEvent(new Event('resize')); - }, []); - - const gridItems = React.Children.map(children.props.children, (child, index) => { - if (!React.isValidElement(child)) return null; - - return React.cloneElement(child, { - key: index === 0 ? 'device-list' : 'device-details', - 'data-grid': layouts.lg[index] - }); - }); - - return ( - <div style={{ display: mounted ? 'block' : 'none' }}> - <ResponsiveGridLayout - className="layout" - layouts={layouts} - breakpoints={{ lg: 996, sm: 480 }} - cols={{ lg: 4, sm: 3 }} - rowHeight={rowHeight} - margin={[20, 20]} - draggableHandle=".drag-handle" - isDraggable={true} - isResizable={true} - preventCollision={true} - compactType={null} - useCSSTransforms={mounted} - resizeHandles={['se']} // Only show resize handle in bottom right corner - > - {gridItems} - </ResponsiveGridLayout> - </div> - ); -}; - -export default GridLayout; \ No newline at end of file diff --git a/react-ui/src/shared/layouts/grid.layout/update-inidicator.layout/update-indicator.layout.tsx b/react-ui/src/shared/layouts/grid.layout/update-inidicator.layout/update-indicator.layout.tsx deleted file mode 100644 index d71bb6cce17eefb53d7ddcdcda9db99545bf6b86..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/layouts/grid.layout/update-inidicator.layout/update-indicator.layout.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { faCircle } from '@fortawesome/free-solid-svg-icons' -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import React, { useState } from 'react' -import { Overlay, Tooltip } from 'react-bootstrap' -import { useTranslation } from 'react-i18next' -import { CategoryType } from '../types' -import { useUpdateIndicatorViewModel } from './update-indicator.viewmodel' - -interface UpdateIndicatorProps { - category: CategoryType - updateInterval: number -} - -const UpdateIndicator: React.FC<UpdateIndicatorProps> = ({ category, updateInterval }) => { - const [showTooltip, setShowTooltip] = useState(false) - const { t } = useTranslation('common') - const target = React.useRef(null) - const { secondsSinceUpdate, getStatusColor } = useUpdateIndicatorViewModel(category) - - return ( - <div - className="position-absolute" - style={{ - top: 0, - right: '40px', - padding: '10px', - zIndex: 10 - }} - > - <div - ref={target} - onMouseEnter={() => setShowTooltip(true)} - onMouseLeave={() => setShowTooltip(false)} - style={{ cursor: 'pointer' }} - > - <FontAwesomeIcon - icon={faCircle} - className={getStatusColor(updateInterval)} - size="sm" - /> - </div> - - <Overlay target={target.current} show={showTooltip} placement="bottom"> - <Tooltip id="update-tooltip"> - {t('device.box.lastUpdate', { seconds: secondsSinceUpdate })} - </Tooltip> - </Overlay> - </div> - ) -} - -export default UpdateIndicator \ No newline at end of file diff --git a/react-ui/src/shared/layouts/grid.layout/update-inidicator.layout/update-indicator.viewmodel.tsx b/react-ui/src/shared/layouts/grid.layout/update-inidicator.layout/update-indicator.viewmodel.tsx deleted file mode 100644 index bb91b0b17e1c4e815d92d14420f0022ddaf819e2..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/layouts/grid.layout/update-inidicator.layout/update-indicator.viewmodel.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useAppSelector } from "@hooks" -import { CategoryType } from "@shared/types/category.type" -import { useEffect, useState } from 'react' - -export const useUpdateIndicatorViewModel = (category: CategoryType) => { - const { thunks } = useAppSelector((state) => state.routine) - const [secondsSinceUpdate, setSecondsSinceUpdate] = useState<number>(-1) - - useEffect(() => { - const updateTimer = () => { - const lastupdate = thunks[category]?.lastupdate - if (lastupdate) { - setSecondsSinceUpdate(Math.round((Date.now() - lastupdate) / 1000)) - } else { - setSecondsSinceUpdate(-1) - } - } - - // Initial update - updateTimer() - - // Set up interval for updates - const intervalId = setInterval(updateTimer, 1000) - - return () => clearInterval(intervalId) - }, [category, thunks]) - - const getStatusColor = (updateInterval: number) => { - const updateIntervalSeconds = updateInterval / 1000 - if (secondsSinceUpdate > updateIntervalSeconds * 0.9) return "text-primary" - if (secondsSinceUpdate > updateIntervalSeconds * 1.3) return "text-danger" - return "text-bg-primary" - } - - return { - secondsSinceUpdate, - getStatusColor - } -} \ No newline at end of file diff --git a/react-ui/src/shared/layouts/protected.layout/protected.layout.scss b/react-ui/src/shared/layouts/protected.layout/protected.layout.scss index 52429031b69b6ef664707c5397f67153c04b08f8..07b38d5a73d1102bebf4e4b0f82e8cc2bc10a799 100755 --- a/react-ui/src/shared/layouts/protected.layout/protected.layout.scss +++ b/react-ui/src/shared/layouts/protected.layout/protected.layout.scss @@ -1,5 +1,7 @@ @import "/src/shared/style/colors.scss"; +$sidebar-width: 4.5em; + .head-links { text-decoration: none; color: map-get($theme-colors, dark); @@ -8,6 +10,7 @@ &:hover { color: map-get($theme-colors, primary); + font-weight: 600; } &.active { @@ -16,46 +19,11 @@ } } -#navbar { - padding: 1em !important; +.sidebar { + width: $sidebar-width; + height: 100vh; } -// Add these styles to your protected.layout.scss -nav { - border-radius: $border-radius $border-radius; - padding: 0 !important; - - .head-links { - text-decoration: none; - color: map-get($theme-colors, "dark"); - padding: 8px 16px; - margin: 0 4px; - border-radius: 12px; - transition: all 0.2s ease; - - &:hover { - background-color: map-get($theme-colors, "bg-primary"); - } - - &.active { - color: map-get($theme-colors, "primary"); - background-color: map-get($theme-colors, "primary::hover"); - } - } - - .dropdown-menu { - border-radius: $border-radius; - box-shadow: $box-shadow; - border: none; - padding: 8px; - - .dropdown-item { - border-radius: 8px; - padding: 8px 16px; - - &:hover { - background-color: map-get($theme-colors, "bg-primary"); - } - } - } +.main-content { + margin-left: $sidebar-width; } diff --git a/react-ui/src/shared/layouts/protected.layout/protected.layout.tsx b/react-ui/src/shared/layouts/protected.layout/protected.layout.tsx index dbcb49192988956ac0aa2eafd4c92575f0364c5c..c7e13c6fe55a2f874c6eeafbc956305986051b63 100755 --- a/react-ui/src/shared/layouts/protected.layout/protected.layout.tsx +++ b/react-ui/src/shared/layouts/protected.layout/protected.layout.tsx @@ -1,16 +1,17 @@ +import logo from '@assets/logo.svg'; +import { fetchPnds } from '@component/devices/reducer/device.reducer'; import { faCircleUser, faRightFromBracket } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { useAppDispatch, useAppSelector } from '@hooks'; import { useAuth } from "@provider/auth.provider"; import { MenuProvider } from '@provider/menu/menu.provider'; import { DEVICE_URL, LOGIN_URL } from '@routes'; -import { fetchPnds, fetchUser } from '@shared/routine/user.routine'; +import { fetchUser } from '@shared/reducer/user.reducer'; import React, { useEffect } from "react"; -import { Col, Container, Dropdown, Row } from "react-bootstrap"; +import { Dropdown } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { Link, Outlet, useNavigate } from "react-router-dom"; import "./protected.layout.scss"; -import logo from '/public/logo.png'; export const ProtectedLayout = () => { @@ -69,44 +70,48 @@ export const ProtectedLayout = () => { } ); + const VerticalSidebar = () => { + return ( + <div className="d-flex fixed-top flex-column flex-shrink-0 bg-white sidebar justify-content-end border-end border-dark py-3 z-2"> + <FontAwesomeIcon className="clickable icon" icon={faRightFromBracket} onClick={logout} size="2x" /> + </div> + ) + } + const HorizontalNavbar = () => { return ( - <Container fluid> - <Row> - <Col> - <nav id="navbar" className="bg-white mx-4 mt-4 d-flex align-items-center c-box"> - <Link to="/"><img src={logo} className="mx-4" width={45} alt="logo" /></Link> - <Link className={"head-links" + handleActiveLink(DEVICE_URL)} to="/">{t('protected.link.device_list')}</Link> - <Link className={"head-links" + handleActiveLink('/map')} to="/">{t('protected.link.map')}</Link> - <Link className={"head-links" + handleActiveLink('/configuration_management')} to="/">{t('protected.link.configuration_mgmt')}</Link> + <nav className="bg-white border-bottom border-dark py-2 d-flex align-items-center z-3 position-relative"> + <Link to="/"><img src={logo} className="mx-4 me-5" width={25} alt="logo" /></Link> + <Link className={"head-links" + handleActiveLink(DEVICE_URL)} to="/">{t('protected.link.device_list')}</Link> + <Link className={"head-links" + handleActiveLink('/map')} to="/">{t('protected.link.map')}</Link> + <Link className={"head-links" + handleActiveLink('/configuration_management')} to="/">{t('protected.link.configuration_mgmt')}</Link> - <Dropdown className="ms-auto px-3"> - <Dropdown.Toggle as={UserIconToggle}> - <FontAwesomeIcon icon={faCircleUser} className="clickable" size="2x" /> - </Dropdown.Toggle> + <Dropdown className="ms-auto px-3"> + <Dropdown.Toggle as={UserIconToggle}> + <FontAwesomeIcon icon={faCircleUser} className="icon clickable" /> + </Dropdown.Toggle> - <Dropdown.Menu as={UserIconMenu}> - <Dropdown.Item eventKey="1">{user?.name}</Dropdown.Item> - <hr /> - <Dropdown.Item eventKey="2"> - <Link className="text-decoration-none text-reset" to="/">{t('protected.link.settings')}</Link> - </Dropdown.Item> - <Dropdown.Item eventKey="3" onClick={logout}> - <Link className="text-decoration-none text-reset" to="/"><FontAwesomeIcon className="clickable" icon={faRightFromBracket} /><span className="ms-1">{t('global.menu_item.logout')}</span></Link> - </Dropdown.Item> - </Dropdown.Menu> - </Dropdown> - </nav> - </Col> - </Row> - </Container> + <Dropdown.Menu as={UserIconMenu}> + <Dropdown.Item eventKey="1">{user?.name}</Dropdown.Item> + <hr /> + <Dropdown.Item eventKey="1"> + <Link className="text-decoration-none text-reset" to="/">{t('protected.link.settings')}</Link> + </Dropdown.Item> + </Dropdown.Menu> + </Dropdown> + </nav> ) } return ( - <MenuProvider> - {HorizontalNavbar()} - <Outlet /> - </MenuProvider> + <div> + <MenuProvider> + {HorizontalNavbar()} + {VerticalSidebar()} + <div className='main-content'> + <Outlet /> + </div> + </MenuProvider> + </div> ) }; \ No newline at end of file diff --git a/react-ui/src/shared/provider/auth.provider.tsx b/react-ui/src/shared/provider/auth.provider.tsx index 77219bdce1547fc11b889eb444ae6d6a9eb5eb6a..69bdccbdfbb3b6db3295d20a137e56a57d8504f2 100755 --- a/react-ui/src/shared/provider/auth.provider.tsx +++ b/react-ui/src/shared/provider/auth.provider.tsx @@ -1,8 +1,8 @@ import { AuthServiceLoginApiArg, AuthServiceLoginApiResponse, useAuthServiceLoginMutation } from "@api/api"; import { getCookieValue } from "@helper/coookie"; +import { BasicProp } from "@helper/interfaces"; import { useAppDispatch, useAppSelector } from "@hooks"; import { DEVICE_URL, LOGIN_URL } from "@routes"; -import { BasicProp } from "@shared/types/interfaces.type"; import { jwtDecode } from "jwt-decode"; import { createContext, useContext, useEffect, useMemo } from "react"; import { useNavigate } from "react-router-dom"; diff --git a/react-ui/src/shared/provider/menu/menu.provider.tsx b/react-ui/src/shared/provider/menu/menu.provider.tsx index f3af8e5c13573e8c317eb27e433ebf63872e093f..a91f46639af28e2db261a3089f62e0a79fc13c41 100644 --- a/react-ui/src/shared/provider/menu/menu.provider.tsx +++ b/react-ui/src/shared/provider/menu/menu.provider.tsx @@ -1,7 +1,7 @@ import { faRightFromBracket, IconDefinition } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { BasicProp } from "@helper/interfaces"; import { useAuth } from "@provider/auth.provider"; -import { BasicProp } from "@shared/types/interfaces.type"; import React, { createContext, useContext, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import './menu.provider.scss'; @@ -19,11 +19,13 @@ type Action = { } interface MenuProviderType { - subscribe: ((value: SubscriptionValue) => MenuSubscription) | null; + subscribe: (value: SubscriptionValue) => MenuSubscription } const MenuContext = createContext<MenuProviderType>({ - subscribe: null + subscribe: function (): MenuSubscription { + throw new Error("Function not implemented."); + } }) interface SubscriptionValue { @@ -31,16 +33,11 @@ interface SubscriptionValue { actions: Array<Action> } -interface SubscriptionMap { - [id: string]: SubscriptionValue -} - export const MenuProvider: React.FC<BasicProp> = ({ children }) => { const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 }); const [showMenu, setShowMenu] = useState(false); - const [subscribedTargets, setSubscribedTargets] = useState<SubscriptionMap>({}); - + const [subscribedTargets, setSubscribedTargets] = useState<Array<SubscriptionValue>>([]) const { logout } = useAuth() const { t } = useTranslation('common') @@ -64,15 +61,12 @@ export const MenuProvider: React.FC<BasicProp> = ({ children }) => { const handleContextMenu = (event: React.MouseEvent<HTMLElement>) => { event.preventDefault(); - - const targets = Object.values(subscribedTargets).filter( - ({ target }) => target.contains(event.target as HTMLElement) - ); + const targets = subscribedTargets.filter(({ target }) => target.contains(event.target as HTMLElement)) setMenuPosition({ top: event.pageY, left: event.pageX }); - setMenuItems(targets); - setClickedHtmlElement(event.target as HTMLElement); - displayMenu(); + setMenuItems(targets) + setClickedHtmlElement(event.target as HTMLElement) + displayMenu() }; const displayMenu = () => { @@ -96,27 +90,20 @@ export const MenuProvider: React.FC<BasicProp> = ({ children }) => { const value = useMemo<MenuProviderType>(() => { return { - subscribe(target: SubscriptionValue) { - const subscriptionId = crypto.randomUUID(); // Generate unique ID + subscribe(target) { + const index = subscribedTargets.length; - setSubscribedTargets(prev => ({ - ...prev, - [subscriptionId]: target - })); + setSubscribedTargets([...subscribedTargets, target]) const subscription: MenuSubscription = { unsubscribe() { - setSubscribedTargets(prev => { - const next = { ...prev }; - delete next[subscriptionId]; - return next; - }); + setSubscribedTargets([...subscribedTargets.splice(index, 1)]) }, } - return subscription; + return subscription }, } as MenuProviderType - }, []); + }, []) return ( <MenuContext.Provider value={value}> diff --git a/react-ui/src/shared/provider/utils.provider.tsx b/react-ui/src/shared/provider/utils.provider.tsx index 3a76bbe69430ca8f5031935f8e1d11925c620610..ca6aa1d3235efe3e1f48441fc7a39a47e9ce7fb7 100644 --- a/react-ui/src/shared/provider/utils.provider.tsx +++ b/react-ui/src/shared/provider/utils.provider.tsx @@ -1,4 +1,4 @@ -import { BasicProp } from "@shared/types/interfaces.type"; +import { BasicProp } from "@helper/interfaces"; import React, { createContext, useContext, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "react-toastify"; diff --git a/react-ui/src/shared/reducer/routine.reducer.ts b/react-ui/src/shared/reducer/routine.reducer.ts index 95d5f06e7d708965ec65b2960f6f56823a976f6b..a52ee84d3f16e89f5e899919d59fcb44428a9213 100755 --- a/react-ui/src/shared/reducer/routine.reducer.ts +++ b/react-ui/src/shared/reducer/routine.reducer.ts @@ -1,42 +1,58 @@ import { PayloadAction, createSlice } from '@reduxjs/toolkit' -import { CategoryType } from '@shared/types/category.type' -import { ThunkDTO, ThunkPersist } from '@shared/types/thunk.type' import { RoutineManager } from '@utils/routine.manager' import { RootState } from '../../stores' import { startListening } from '../../stores/middleware/listener.middleware' import { setToken } from './user.reducer' +interface ThunkEntityDTO { + thunk: any + payload: Object + + /** + * Only one subscription per category is allowed. New subscription will unsubscribe and overwrite the old one + */ + category: CATEGORIES +} + +interface ThunkEntity extends ThunkEntityDTO { + id?: number + locked: boolean +} + export interface ReducerState { - thunks: Record<CategoryType, ThunkPersist | null> + thunks: { [key in keyof typeof CATEGORIES]: ThunkEntity | null } +} + +export enum CATEGORIES { + TABLE, + TAB, } const initialState: ReducerState = { thunks: { - DEVICE: null, TABLE: null, - TAB: null + TAB: null, }, - } - const RoutineSlice = createSlice({ name: 'routine', initialState, reducers: { - addRoutine: (state: any, { payload }: PayloadAction<ThunkDTO>) => { - const thunk: ThunkPersist = { - category: payload.category, - payload: payload.payload, - thunkId: payload.thunk.id, - lastupdate: Date.now() - } - - state.thunks[payload.category] = thunk + addRoutine: (state: any, { payload }: PayloadAction<ThunkEntityDTO>) => { + const newThunk: ThunkEntity = { ...payload, locked: true } + state.thunks[CATEGORIES[payload.category]] = newThunk }, - refreshUpdateTimer: (state: any, { payload }: PayloadAction<CategoryType>) => { - state.thunks[payload].lastupdate = Date.now() + setThunkId: (state, { payload }: PayloadAction<{ id: number; category: CATEGORIES }>) => { + const thunk = state.thunks[CATEGORIES[payload.category] as any] + + if (!thunk) { + // TODO + throw new Error('Thunk not found') + } + + state.thunks[CATEGORIES[payload.category] as any] = { ...thunk, id: payload.id, locked: false } }, removeAll: (state) => { @@ -45,7 +61,7 @@ const RoutineSlice = createSlice({ }, }) -export const { addRoutine, refreshUpdateTimer } = RoutineSlice.actions +export const { addRoutine } = RoutineSlice.actions // on logout remove all routine startListening({ @@ -56,37 +72,49 @@ startListening({ }, }) -/** - * Add new routine - * - * This listener handles the connection between the RoutingManager that - * stores the non persistable thunk object and the peristable thunk information. - * The persistable information are stored in this reducer - */ +// on rehydrate add all persistet routines +// TODO -> thunk does not have the thunk function object due to its coming from the store that ignores the value. +// at this point we have to figure out how to get the thunk function out of the "string" name +// startListening({ +// predicate: ({ type }) => type === REHYDRATE, +// effect: async (_, listenerApi) => { +// const { routine } = listenerApi.getState() as RootState +// for (const [_, thunk] of Object.entries<ThunkEntity>(routine.thunks)) { +// if (!thunk) { +// continue +// } +// const dto: ThunkEntityDTO = thunk +// listenerApi.dispatch(addRoutine(dto)) +// } +// }, +// }) + +// add new routine startListening({ predicate: (action) => addRoutine.match(action), effect: async (action, listenerApi) => { - const { thunk } = action.payload as ThunkDTO + const { thunk } = action.payload as ThunkEntity const subscription = await listenerApi.dispatch(thunk(action.payload.payload)) - - if (subscription.error) { - throw new Error('Error during routine execution: ' + subscription.error.message) - } - - RoutineManager.add(subscription.payload, action.payload.category) + const thunkId = await RoutineManager.add(subscription.payload) + listenerApi.dispatch( + RoutineSlice.actions.setThunkId({ id: thunkId, category: action.payload.category }) + ) }, }) -// unsubscribe old routine that is in the same category +// unsubscribe old routine startListening({ predicate: (action) => addRoutine.match(action), effect: async (action, listenerApi) => { const { routine } = listenerApi.getOriginalState() as RootState - const category = action.payload.category; - - const lastThunk = routine.thunks[category as CategoryType] + const lastThunk = routine.thunks[CATEGORIES[action.payload.category] as any] if (lastThunk) { - RoutineManager.unsubscribe(category) + if (!lastThunk.id) { + throw new Error() + // TODO + } + + RoutineManager.unsubscribe(lastThunk.id) } }, }) diff --git a/react-ui/src/shared/reducer/user.reducer.ts b/react-ui/src/shared/reducer/user.reducer.ts index a0f2d422255f9a135236c4d8c2e3a70a5e51ec46..af0f2d171675627a0a69f5aebd6e7b64125fba37 100755 --- a/react-ui/src/shared/reducer/user.reducer.ts +++ b/react-ui/src/shared/reducer/user.reducer.ts @@ -1,6 +1,7 @@ -import { RbacUser } from '@api/api' +import { api, RbacUser, UserServiceGetUsersApiArg } from '@api/api' import { setCookieValue } from '@helper/coookie' -import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit' +import { RootState } from '..' export interface UserSliceState { // defined by the frontend user input. This value is getting compared with the backend response @@ -33,4 +34,27 @@ export const { setToken } = userSlice.actions export const { setUser } = userSlice.actions export default userSlice.reducer -export const userReducerPath = userSlice.reducerPath \ No newline at end of file +export const userReducerPath = userSlice.reducerPath + +export const fetchUser = createAsyncThunk('user/fetchUser', (_, thunkAPI) => { + const payload: UserServiceGetUsersApiArg = {} + + thunkAPI.dispatch(api.endpoints.userServiceGetUsers.initiate(payload)).then((response) => { + if (response.error || !response.data?.user?.length) { + // TODO proper error handling + throw new Error('Fetching the pnd list after successful login failed') + } + + const { user } = thunkAPI.getState() as RootState + + // TODO ask if this is the correct approach + const matchedUser = response.data.user.find((_user) => _user.name === user.username) + + if (!matchedUser) { + // TODO proper error handling + throw new Error('No user found with the provided username') + } + + thunkAPI.dispatch(setUser(matchedUser)) + }) +}) diff --git a/react-ui/src/shared/routine/user.routine.ts b/react-ui/src/shared/routine/user.routine.ts deleted file mode 100644 index 368ccf41246176c533b2394a581124200668f527..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/routine/user.routine.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { api, PndServiceGetPndListApiArg, UserServiceGetUsersApiArg } from "@api/api" -import { setPnds } from "@component/devices/reducer/device.reducer" -import { createAsyncThunk } from "@reduxjs/toolkit" -import { setUser } from "@shared/reducer/user.reducer" -import { RootState } from "src/stores" - -export const fetchUser = createAsyncThunk('user/fetchUser', (_, thunkAPI) => { - const payload: UserServiceGetUsersApiArg = {} - - thunkAPI.dispatch(api.endpoints.userServiceGetUsers.initiate(payload)).then((response) => { - if (response.error || !response.data?.user?.length) { - // TODO proper error handling - throw new Error('Fetching the pnd list after successful login failed') - } - - const { user } = thunkAPI.getState() as RootState - const matchedUser = response.data.user.find((_user) => _user.name === user.username) - - if (!matchedUser) { - // TODO proper error handling => logout - throw new Error('No user found with the provided username') - } - - thunkAPI.dispatch(setUser(matchedUser)) - }) -}) - -export const fetchPnds = createAsyncThunk('device/fetchPnds', (_, thunkApi) => { - const payload: PndServiceGetPndListApiArg = { - timestamp: new Date().getTime().toString(), - } - - const subscription = thunkApi.dispatch(api.endpoints.pndServiceGetPndList.initiate(payload)) - subscription.unwrap().then((response) => { - thunkApi.dispatch(setPnds(response.pnd)) - }) -}) \ No newline at end of file diff --git a/react-ui/src/shared/style/box.scss b/react-ui/src/shared/style/box.scss index 53158f51586c931dc7855c8423b2d40f7371c2bc..bd75fb00a6c1574584c85afafe6a91d8b858deb4 100755 --- a/react-ui/src/shared/style/box.scss +++ b/react-ui/src/shared/style/box.scss @@ -1,59 +1,26 @@ -@import "./colors.scss"; +@import './colors.scss'; $box-padding: 10px; -$border-radius: 0.25em; -$border-width: 2px; -$transition-duration: 0.3s; +$border-radius: 20px; + .c-box { padding: $box-padding; background-color: white; - position: relative; + box-shadow: 0px 4px 4px rgba(0,0,0, .35); border-radius: $border-radius; - transition: box-shadow $transition-duration ease-in-out; - - background: - linear-gradient(white, white) padding-box, - linear-gradient( - 180deg, - rgba(map-get($theme-colors, "primary"), 0.4) 0%, - rgba(map-get($theme-colors, "primary"), 0.2) 40%, - rgba(map-get($theme-colors, "primary"), 0.1) 100% - ) - border-box; - border: $border-width solid transparent; - box-shadow: $box-shadow; - - &::before { - content: ""; - position: absolute; - top: -$border-width; - left: -$border-width; - right: -$border-width; - bottom: -$border-width; - background: linear-gradient( - 180deg, - rgba(map-get($theme-colors, "primary"), 0.4) 0%, - rgba(map-get($theme-colors, "primary"), 0.2) 60%, - rgba(map-get($theme-colors, "primary"), 0.1) 100% - ); - border-radius: inherit; - z-index: -1; - opacity: 0; - transition: opacity $transition-duration ease-in-out; - } - - &:hover { - box-shadow: 0 0.5rem 1rem rgba(map-get($theme-colors, "primary"), 0.2); - - &::before { - opacity: 1; - } - } } .abstract-box { - padding: $box-padding; - font-size: 0.9em; + padding: 16px $box-padding; + font-size: .90em; border-radius: calc($border-radius / 2); } + + +// @each $color, $value in $theme-colors { +// .#{$color}-box { +// @extend .abstract-box; +// background-color: $value !important; +// } +// } diff --git a/react-ui/src/shared/style/colors.scss b/react-ui/src/shared/style/colors.scss index 4469a4a5b9fc0c32733bcb0afe112f568a5e5492..749af9e8eb8e86b0169c7f2a652dc0bada992181 100755 --- a/react-ui/src/shared/style/colors.scss +++ b/react-ui/src/shared/style/colors.scss @@ -1,13 +1,11 @@ $theme-colors: ( - "primary": #b350e0, - "primary::hover": #ddaff3af, - "bg-primary": #ededed, - "danger": #ff0000, - "warning": #dbd116, - "dark": #595959, - "black": #000000 + 'primary': #b350e0, + 'primary::hover': #ddaff3af, + 'bg-primary': #E1E1E1, + 'danger': #ffdcdc, + 'warning': #dbd116, + 'dark': #595959, + 'black': #000000, ); - -$box-shadow: 0px 4px 8px rgba(map-get($theme-colors, "primary"), 0.2); - -@import "/node_modules/bootstrap/scss/bootstrap"; + +@import '/node_modules/bootstrap/scss/bootstrap'; diff --git a/react-ui/src/shared/style/fonts.scss b/react-ui/src/shared/style/fonts.scss index 3af44e1559b21507bd509acb4c954a204250f3ba..c47d1a52fb36da1863fc511e7e1e4deb045694cf 100755 --- a/react-ui/src/shared/style/fonts.scss +++ b/react-ui/src/shared/style/fonts.scss @@ -1,12 +1,9 @@ @font-face { font-family: Inter; - src: - url("/fonts/inter-webfont.woff2") format("woff2"), - url("/fonts/inter-webfont.woff") format("woff"); - font-weight: normal; - font-style: normal; + src: url("/fonts/Inter.ttf"); } + * { font-family: Inter; -} +} \ No newline at end of file diff --git a/react-ui/src/shared/style/utils.scss b/react-ui/src/shared/style/utils.scss index d6f34d301416ac7aeb82aa740cd150e95efbd6c5..d8be654f7e67fc11d9626c2a52f5744af0dfbb79 100755 --- a/react-ui/src/shared/style/utils.scss +++ b/react-ui/src/shared/style/utils.scss @@ -7,3 +7,7 @@ cursor: pointer; } } + +.icon { + font-size: 1.75em; +} diff --git a/react-ui/src/shared/types/category.type.ts b/react-ui/src/shared/types/category.type.ts deleted file mode 100644 index e08282341e803cf8b62bf0deec423fb0ebe62234..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/types/category.type.ts +++ /dev/null @@ -1,17 +0,0 @@ - -const DeviceListView = { - TABLE: "device_list/table", - TAB: "device_list/tab", -} - -const Shared = { - DEVICE: 'objects/device' -} - - -export const Category = { - ...DeviceListView, - ...Shared -} - -export type CategoryType = keyof typeof Category \ No newline at end of file diff --git a/react-ui/src/shared/types/thunk.type.ts b/react-ui/src/shared/types/thunk.type.ts deleted file mode 100644 index ff037d79655b7596295b387c61de4755dcc2eff4..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/types/thunk.type.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { CategoryType } from "./category.type" - - -// The actual Thunk type is hard to determine because is very generic -export type ThunkFunc = any - - -export interface ThunkDTO { - thunk: ThunkFunc - payload: Object - - /** - * Only one subscription per category is allowed. - * New subscription will unsubscribe and overwrite the old one - */ - category: CategoryType -} - -export interface ThunkPersist { - thunkId: number, - payload: Object - category: CategoryType, - lastupdate: number -} diff --git a/react-ui/src/shared/utils/loading-fallback.tsx b/react-ui/src/shared/utils/loading-fallback.tsx deleted file mode 100644 index d1c6daa4aa369ca157dbae653c6dd29f6f99d99a..0000000000000000000000000000000000000000 --- a/react-ui/src/shared/utils/loading-fallback.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Col, Container, Row } from 'react-bootstrap'; -import logo from '/public/logo.png'; - -interface DelayedRenderProps { - children: React.ReactNode; - loading?: { - minimumLoadingTime: number; - component: () => JSX.Element - } -} - -export const SplashScreen = () => { - const [dots, setDots] = useState(''); - - useEffect(() => { - const dotsInterval = setInterval(() => { - setDots(prev => prev.length >= 3 ? '' : prev + '.'); - }, 500); - - return () => clearInterval(dotsInterval); - }, []); - - return ( - <div className="splash-screen-overlay"> - <Container fluid className="h-100 d-flex align-items-center justify-content-center bg-bg-primary"> - <Row> - <Col className="text-center"> - <div className="loading-bounce mb-4"> - <img - src={logo} - alt="Logo" - className="img-fluid" - style={{ width: '120px', height: '120px', objectFit: 'contain' }} - /> - </div> - <div className="loading-text"> - <span className="h4 text-secondary">Loading</span> - <span className="h4 text-secondary dots-width">{dots}</span> - </div> - </Col> - </Row> - </Container> - - <style> - {` - .splash-screen-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: #f8f9fa; - z-index: 0; - display: flex; - align-items: center; - justify-content: center; - } - - .loading-bounce { - animation: bounce 1s infinite; - } - - @keyframes bounce { - 0%, 100% { - transform: translateY(0); - } - 50% { - transform: translateY(-20px); - } - } - - .loading-text { - display: flex; - justify-content: center; - align-items: center; - } - - .dots-width { - min-width: 24px; - text-align: left; - margin-left: 2px; - } - `} - </style> - </div> - ); -}; - -export const DelayedRender: React.FC<DelayedRenderProps> = ({ - children, - loading -}) => { - const [shouldRender, setShouldRender] = useState(false); - - useEffect(() => { - if (!loading) { - setShouldRender(true); - return; - } - - const timer = setTimeout(() => { - setShouldRender(true); - }, loading.minimumLoadingTime); - - return () => clearTimeout(timer); - }, [loading]); - - if (!shouldRender && loading) { - const LoadingComponent = loading.component; - return <LoadingComponent />; - } - - return <>{children}</>; -}; - -export default DelayedRender; \ No newline at end of file diff --git a/react-ui/src/shared/utils/routine.manager.ts b/react-ui/src/shared/utils/routine.manager.ts index 1fee6723e7f36531f330160aadfc77d60ebfce82..ade07928976f5be714475325fceb2003ab6e75f0 100755 --- a/react-ui/src/shared/utils/routine.manager.ts +++ b/react-ui/src/shared/utils/routine.manager.ts @@ -1,75 +1,67 @@ -import { infoMessage, warnMessage } from '@helper/debug' import { QueryActionCreatorResult } from '@reduxjs/toolkit/query' -import { Category, CategoryType } from '@shared/types/category.type' type Routine = QueryActionCreatorResult<any> interface Entity { routine: Routine + id: number } - -interface RoutineState { - routines: Record<CategoryType, Entity | null> -} - -const initalState: RoutineState = { - routines: { - DEVICE: null, - TABLE: null, - TAB: null - } +const initialState = { + routines: [] as Entity[], } /** -* Routine manager is a singleton that holds all running routines. -* The redux store holds any persistable information about the routines. -* The routine objects itself are stored in the RoutineManager. -*/ + * Routine manager is a singleton that holds all running routines. + * The redux store holds any persistable information about the routines. + * The routines objects itself are stored in the RoutineManager. + */ export const RoutineManager = (() => { - let state = initalState + const state = initialState + const add = (routine: Routine): number => { + const id = state.routines.length - const add = (routine: Routine, category: CategoryType): boolean => { - const entity: Entity = { + const newEntity: Entity = { routine: routine, + id, } - state.routines = { - ...state.routines, - [category]: entity - } - infoMessage("Routine subscribed to category " + category) + state.routines = [...state.routines, newEntity] - return true + return id } const unsubscribeAll = () => { - Object.keys(state.routines) - .forEach((category) => { - unsubscribe(category as CategoryType) - }) + state.routines.forEach(({ routine: subscription }) => { + _unsubscribe(subscription) + }) - state = initalState + state.routines = initialState.routines } /** * @param id * @returns returns true if the routine was stopped, false if it was not found */ - const unsubscribe = (category: CategoryType): boolean => { - const entity = state.routines[category] + const unsubscribe = (id: number): boolean => { + const routine = state.routines.find(({ id: routineId }) => routineId === id) - if (entity) { - entity.routine.unsubscribe() - state.routines[category] = null - infoMessage("Routine unsubscribed from category " + category) + if (routine) { + _unsubscribe(routine.routine) } - if (!!entity) { - warnMessage("Desired routine to unsubscribe does not exist in category " + Category[category]) - } + return !!routine + } - return !!entity + /** + * Actual unsubscribe process. + * This process is extracted to have a single process of unsubscribing. + * + * @param subscription + */ + const _unsubscribe = (subscription: Routine) => { + subscription.unsubscribe() + // TODO remove from state } return { @@ -77,4 +69,4 @@ export const RoutineManager = (() => { unsubscribe, unsubscribeAll, } -})() \ No newline at end of file +})() diff --git a/react-ui/tsconfig.json b/react-ui/tsconfig.json index d40af31cce3f086a5b0b5c6870cfad85ce4d50a8..c679b7100669ddf234324cecde3dd37ab94c95d7 100755 --- a/react-ui/tsconfig.json +++ b/react-ui/tsconfig.json @@ -22,6 +22,7 @@ "baseUrl": ".", "paths": { + "@assets/*": ["assets/*"], "@api/*": ["src/shared/api/*"], "@reducer/*": ["src/stores/reducer/*"], "@provider/*": ["src/shared/provider/*"], diff --git a/react-ui/vite.config.mjs b/react-ui/vite.config.mjs index 33fd69cc76fd094e53f168c6babdf0da367d897c..fefafe05a692ebf39a8879ac440fcff0f549ec60 100755 --- a/react-ui/vite.config.mjs +++ b/react-ui/vite.config.mjs @@ -1,39 +1,17 @@ -import react from '@vitejs/plugin-react'; -import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' export default defineConfig({ plugins: [react()], build: { - sourcemap: false, - - rollupOptions: { - output: { - manualChunks: { - 'required': [ - 'bootstrap', 'react-bootstrap', - 'react', 'react-dom', 'react-router-dom', - 'redux', '@reduxjs/toolkit', 'react-redux', 'redux-observable', 'redux-persist', - 'i18next', 'react-i18next', - '@fortawesome/fontawesome-svg-core', - '@fortawesome/free-regular-svg-icons', - '@fortawesome/free-solid-svg-icons', - '@fortawesome/react-fontawesome' - ], - 'lazy': [ - 'react-toastify' - ] - } - } - } + sourcemap: true, }, // develop server server: { - sourcemap: true, - host: true, port: 3000, proxy: { '/api': { - target: 'http://clab-gosdn_csbi_arista_base-gosdn:8080', + target: 'http://127.0.0.1:8080', changeOrigin: true, secure: false, rewrite: (path) => path.replace(/^\/api/, ''), @@ -57,6 +35,7 @@ export default defineConfig({ }, resolve: { alias: { + '@assets': '/assets', '@api': '/src/shared/api', '@reducer': '/src/stores/reducer', '@provider': '/src/shared/provider', diff --git a/react-ui/yarn.lock b/react-ui/yarn.lock index db8fb7d3c16b6ee32f476f99416a6a89236a6891..d054db6d6ed77067217fd0ca327f855376706356 100755 --- a/react-ui/yarn.lock +++ b/react-ui/yarn.lock @@ -1486,13 +1486,6 @@ dependencies: prop-types "^15.8.1" -"@fullhuman/postcss-purgecss@^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@fullhuman/postcss-purgecss/-/postcss-purgecss-7.0.2.tgz#ccacdbc312248c76c42cfac359f4ca5121001e67" - integrity sha512-U4zAXNaVztbDxO9EdcLp51F3UxxYsb/7DN89rFxFJhfk2Wua2pvw2Kf3HdspbPhW/wpHjSjsxWYoIlbTgRSjbQ== - dependencies: - purgecss "^7.0.2" - "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" @@ -2593,13 +2586,6 @@ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.3.tgz#3654138d0da1b0c7916f6ed0dc1cc2b576d47650" integrity sha512-uTYkxTLkYp41nq/ULXyXMtkNT1vu5fXJoqad6uTNCOGat5t9cLgF4vMNLBXsTOXpdOI44XzKPY1M5RRm0bQHuw== -"@types/react-grid-layout@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/react-grid-layout/-/react-grid-layout-1.3.5.tgz#f4b52bf27775290ee0523214be0987be14e66823" - integrity sha512-WH/po1gcEcoR6y857yAnPGug+ZhkF4PaTUxgAbwfeSH/QOgVSakKHBXoPGad/sEznmkiaK3pqHk+etdWisoeBQ== - dependencies: - "@types/react" "*" - "@types/react-transition-group@^4.4.6": version "4.4.11" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.11.tgz#d963253a611d757de01ebb241143b1017d5d63d5" @@ -3850,12 +3836,7 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clsx@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== - -clsx@^2.0.0, clsx@^2.1.0: +clsx@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== @@ -3925,11 +3906,6 @@ combined-stream@^1.0.8: dependencies: delayed-stream "~1.0.0" -commander@^12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" - integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== - commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -4085,11 +4061,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5: shebang-command "^2.0.0" which "^2.0.1" -crypto-js@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" - integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== - crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -5363,11 +5334,6 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== -fast-equals@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-4.0.3.tgz#72884cc805ec3c6679b99875f6b7654f39f0e8c7" - integrity sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg== - fast-glob@^3.2.9, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" @@ -5724,18 +5690,6 @@ glob@^10.3.10: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.0.tgz#6031df0d7b65eaa1ccb9b29b5ced16cea658e77e" - integrity sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g== - dependencies: - foreground-child "^3.1.0" - jackspeak "^4.0.1" - minimatch "^10.0.0" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^2.0.0" - glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -6515,13 +6469,6 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jackspeak@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015" - integrity sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw== - dependencies: - "@isaacs/cliui" "^8.0.2" - jake@^10.8.5: version "10.9.2" resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" @@ -7330,11 +7277,6 @@ lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== -lru-cache@^11.0.0: - version "11.0.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.2.tgz#fbd8e7cf8211f5e7e5d91905c415a3f55755ca39" - integrity sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7465,13 +7407,6 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@^10.0.0: - version "10.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b" - integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ== - dependencies: - brace-expansion "^2.0.1" - minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -7988,14 +7923,6 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-scurry@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" - integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== - dependencies: - lru-cache "^11.0.0" - minipass "^7.1.2" - path-to-regexp@0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" @@ -8697,7 +8624,7 @@ prop-types-extra@^1.1.0: react-is "^16.3.2" warning "^4.0.0" -prop-types@15.x, prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -8726,16 +8653,6 @@ punycode@^2.1.0, punycode@^2.1.1, punycode@^2.3.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -purgecss@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-7.0.2.tgz#b7dccc3ead65a4301eed98e014793719a511c633" - integrity sha512-4Ku8KoxNhOWi9X1XJ73XY5fv+I+hhTRedKpGs/2gaBKU8ijUiIKF/uyyIyh7Wo713bELSICF5/NswjcuOqYouQ== - dependencies: - commander "^12.1.0" - glob "^11.0.0" - postcss "^8.4.47" - postcss-selector-parser "^6.1.2" - q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -8855,14 +8772,6 @@ react-dom@^18.3.1: loose-envify "^1.1.0" scheduler "^0.23.2" -react-draggable@^4.0.3, react-draggable@^4.4.5: - version "4.4.6" - resolved "https://registry.yarnpkg.com/react-draggable/-/react-draggable-4.4.6.tgz#63343ee945770881ca1256a5b6fa5c9f5983fe1e" - integrity sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw== - dependencies: - clsx "^1.1.1" - prop-types "^15.8.1" - react-error-boundary@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.1.2.tgz#bc750ad962edb8b135d6ae922c046051eb58f289" @@ -8875,18 +8784,6 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-grid-layout@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-1.5.0.tgz#b6cc9412b58cf8226aebc0df7673d6fa782bdee2" - integrity sha512-WBKX7w/LsTfI99WskSu6nX2nbJAUD7GD6nIXcwYLyPpnslojtmql2oD3I2g5C3AK8hrxIarYT8awhuDIp7iQ5w== - dependencies: - clsx "^2.0.0" - fast-equals "^4.0.3" - prop-types "^15.8.1" - react-draggable "^4.4.5" - react-resizable "^3.0.5" - resize-observer-polyfill "^1.5.1" - react-i18next@^15.0.0: version "15.1.4" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.1.4.tgz#65c03c31a5e42202000652e163f22f23a9306a60" @@ -8933,14 +8830,6 @@ react-refresh@^0.14.2: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.2.tgz#3833da01ce32da470f1f936b9d477da5c7028bf9" integrity sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA== -react-resizable@^3.0.5: - version "3.0.5" - resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-3.0.5.tgz#362721f2efbd094976f1780ae13f1ad7739786c1" - integrity sha512-vKpeHhI5OZvYn82kXOs1bC8aOXktGU5AmKAgaZS4F5JPburCtbmDPqE7Pzp+1kN4+Wb81LlF33VpGwWwtXem+w== - dependencies: - prop-types "15.x" - react-draggable "^4.0.3" - react-router-dom@^6.23.1: version "6.28.0" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.28.0.tgz#f73ebb3490e59ac9f299377062ad1d10a9f579e6" @@ -9234,11 +9123,6 @@ reselect@^5.1.0: resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e" integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w== -resize-observer-polyfill@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" - integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== - resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"