.
diff --git a/modules/web_server/static/GifLoader.js/README.md b/modules/web_server/static/GifLoader.js/README.md
deleted file mode 100644
index 62b3b16..0000000
--- a/modules/web_server/static/GifLoader.js/README.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# GifLoader.js
-
-Simple library in pure JavaScript to display an automatic loader component while the HTML page is loading.
-
-
-
-### Usage
-
-Include `loader.css` and `loader.js` files in your HTML:
-
-```html
-
-
-```
-
-Everything is automatic, just create new class instance in HTML ``:
-
-```html
-
-```
-
-Loader constructor takes two arguments: `new Loader(imageUrl, autoHide)`
-
-In the first argument, you can specify your own image. In the second argument,
-you can set if loader should automatically hide when the page is fully loaded.
-
-You can hide loader manually:
-
-```html
-
-```
\ No newline at end of file
diff --git a/modules/web_server/static/GifLoader.js/example.html b/modules/web_server/static/GifLoader.js/example.html
deleted file mode 100644
index 80417a3..0000000
--- a/modules/web_server/static/GifLoader.js/example.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
- Title
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/modules/web_server/static/GifLoader.js/src/css/GifLoader.css b/modules/web_server/static/GifLoader.js/src/css/GifLoader.css
deleted file mode 100644
index f7f3c80..0000000
--- a/modules/web_server/static/GifLoader.js/src/css/GifLoader.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.preload, .preload * {
- overflow: hidden;
- transition: none !important;
- -webkit-transition: none !important;
- -moz-transition: none !important;
- -o-transition: none !important;
-}
-
-#loader {
- position: fixed;
- display: flex;
- width: 100vw;
- height: 100vh;
- top: 0;
- left: 0;
- background: white;
- z-index: 5000;
- overflow: hidden;
- transition: opacity 0.8s cubic-bezier(0.62, 1.71, 1, 1.64), margin-top 0.3s step-start;
- transition-timing-function: cubic-bezier(0.62, 1.71, 1, 1.64), steps(1, start);
- opacity: 1;
-}
-#loader img {
- margin: auto;
- max-width: 100px;
- transition: transform 0.2s cubic-bezier(0.5, -0.7, 0.6, 1);
-}
-
-.loader_hidden {
- opacity: 0 !important;
- margin-top: -1000%;
- transition-timing-function: cubic-bezier(0.62, 1.71, 1, 1.64), step-end !important;
-}
-.loader_hidden img {
- transform: scale(0);
-}
-
-/*# sourceMappingURL=GifLoader.css.map */
diff --git a/modules/web_server/static/GifLoader.js/src/css/GifLoader.css.map b/modules/web_server/static/GifLoader.js/src/css/GifLoader.css.map
deleted file mode 100644
index 6d52ada..0000000
--- a/modules/web_server/static/GifLoader.js/src/css/GifLoader.css.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sourceRoot":"","sources":["GifLoader.sass"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EACA;EACA;;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;;AAEJ;EACE;EACA;EACA;;AAEA;EACE","file":"GifLoader.css"}
\ No newline at end of file
diff --git a/modules/web_server/static/GifLoader.js/src/css/GifLoader.sass b/modules/web_server/static/GifLoader.js/src/css/GifLoader.sass
deleted file mode 100644
index ba8b0fc..0000000
--- a/modules/web_server/static/GifLoader.js/src/css/GifLoader.sass
+++ /dev/null
@@ -1,33 +0,0 @@
-.preload, .preload *
- transition: none !important
- overflow: hidden
- -webkit-transition: none !important
- -moz-transition: none !important
- -o-transition: none !important
-
-#loader
- display: flex
- position: fixed
- top: 0
- left: 0
- width: 100vw
- height: 100vh
- transition: opacity 0.8s cubic-bezier(0.62, 1.71, 1, 1.64), margin-top 0.3s step-start
- transition-timing-function: cubic-bezier(0.62, 1.71, 1, 1.64), steps(1, start)
- background: #fff
- opacity: 1
- overflow: hidden
- z-index: 5000
-
- img
- max-width: 100px
- margin: auto
- transition: transform 0.2s cubic-bezier(.5,-.7,.6,1)
-
-.loader_hidden
- margin-top: -1000%
- transition-timing-function: cubic-bezier(0.62, 1.71, 1, 1.64), step-end !important
- opacity: 0 !important
-
- img
- transform: scale(0)
diff --git a/modules/web_server/static/GifLoader.js/src/images/hex-loader.gif b/modules/web_server/static/GifLoader.js/src/images/hex-loader.gif
deleted file mode 100644
index 54f4649..0000000
Binary files a/modules/web_server/static/GifLoader.js/src/images/hex-loader.gif and /dev/null differ
diff --git a/modules/web_server/static/fonts/Cantarell-Bold.otf b/modules/web_server/static/fonts/Cantarell-Bold.otf
deleted file mode 100644
index 1a5954b..0000000
Binary files a/modules/web_server/static/fonts/Cantarell-Bold.otf and /dev/null differ
diff --git a/modules/web_server/static/fonts/Cantarell-ExtraBold.otf b/modules/web_server/static/fonts/Cantarell-ExtraBold.otf
deleted file mode 100644
index e7b5213..0000000
Binary files a/modules/web_server/static/fonts/Cantarell-ExtraBold.otf and /dev/null differ
diff --git a/modules/web_server/static/fonts/Cantarell-Light.otf b/modules/web_server/static/fonts/Cantarell-Light.otf
deleted file mode 100644
index 5f0fa78..0000000
Binary files a/modules/web_server/static/fonts/Cantarell-Light.otf and /dev/null differ
diff --git a/modules/web_server/static/fonts/Cantarell-Regular.otf b/modules/web_server/static/fonts/Cantarell-Regular.otf
deleted file mode 100644
index d92de43..0000000
Binary files a/modules/web_server/static/fonts/Cantarell-Regular.otf and /dev/null differ
diff --git a/modules/web_server/static/fonts/Cantarell-Thin.otf b/modules/web_server/static/fonts/Cantarell-Thin.otf
deleted file mode 100644
index fe58785..0000000
Binary files a/modules/web_server/static/fonts/Cantarell-Thin.otf and /dev/null differ
diff --git a/modules/web_server/static/fonts/IndieFlower-Regular.ttf b/modules/web_server/static/fonts/IndieFlower-Regular.ttf
deleted file mode 100644
index 1070aac..0000000
Binary files a/modules/web_server/static/fonts/IndieFlower-Regular.ttf and /dev/null differ
diff --git a/modules/web_server/static/fonts/RobotoMono-Bold.ttf b/modules/web_server/static/fonts/RobotoMono-Bold.ttf
deleted file mode 100644
index 900fce6..0000000
Binary files a/modules/web_server/static/fonts/RobotoMono-Bold.ttf and /dev/null differ
diff --git a/modules/web_server/static/fonts/RobotoMono-Medium.ttf b/modules/web_server/static/fonts/RobotoMono-Medium.ttf
deleted file mode 100644
index 8461be7..0000000
Binary files a/modules/web_server/static/fonts/RobotoMono-Medium.ttf and /dev/null differ
diff --git a/modules/web_server/static/fonts/RobotoMono-Regular.ttf b/modules/web_server/static/fonts/RobotoMono-Regular.ttf
deleted file mode 100644
index 7c4ce36..0000000
Binary files a/modules/web_server/static/fonts/RobotoMono-Regular.ttf and /dev/null differ
diff --git a/modules/web_server/static/images/icons/actions.png b/modules/web_server/static/images/icons/actions.png
deleted file mode 100644
index d72a6fd..0000000
Binary files a/modules/web_server/static/images/icons/actions.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/arrow.png b/modules/web_server/static/images/icons/arrow.png
deleted file mode 100644
index 14b8970..0000000
Binary files a/modules/web_server/static/images/icons/arrow.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/ban.png b/modules/web_server/static/images/icons/ban.png
deleted file mode 100644
index 0ff666e..0000000
Binary files a/modules/web_server/static/images/icons/ban.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/close.png b/modules/web_server/static/images/icons/close.png
deleted file mode 100644
index 0d7ac3e..0000000
Binary files a/modules/web_server/static/images/icons/close.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/close_thin.png b/modules/web_server/static/images/icons/close_thin.png
deleted file mode 100644
index 159b71c..0000000
Binary files a/modules/web_server/static/images/icons/close_thin.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/data.png b/modules/web_server/static/images/icons/data.png
deleted file mode 100644
index e42536c..0000000
Binary files a/modules/web_server/static/images/icons/data.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/details.png b/modules/web_server/static/images/icons/details.png
deleted file mode 100644
index 5f0022c..0000000
Binary files a/modules/web_server/static/images/icons/details.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/empty.png b/modules/web_server/static/images/icons/empty.png
deleted file mode 100644
index 01411a1..0000000
Binary files a/modules/web_server/static/images/icons/empty.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/handlers.png b/modules/web_server/static/images/icons/handlers.png
deleted file mode 100644
index 84d9d60..0000000
Binary files a/modules/web_server/static/images/icons/handlers.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/info_blue.png b/modules/web_server/static/images/icons/info_blue.png
deleted file mode 100644
index 921f545..0000000
Binary files a/modules/web_server/static/images/icons/info_blue.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/inspect.png b/modules/web_server/static/images/icons/inspect.png
deleted file mode 100644
index 49a0ad2..0000000
Binary files a/modules/web_server/static/images/icons/inspect.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/inspector.png b/modules/web_server/static/images/icons/inspector.png
deleted file mode 100644
index b84abc5..0000000
Binary files a/modules/web_server/static/images/icons/inspector.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/menu.png b/modules/web_server/static/images/icons/menu.png
deleted file mode 100644
index 529ef2c..0000000
Binary files a/modules/web_server/static/images/icons/menu.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/overview.png b/modules/web_server/static/images/icons/overview.png
deleted file mode 100644
index 438d41d..0000000
Binary files a/modules/web_server/static/images/icons/overview.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/plus.png b/modules/web_server/static/images/icons/plus.png
deleted file mode 100644
index d7568fa..0000000
Binary files a/modules/web_server/static/images/icons/plus.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/plus_bold.png b/modules/web_server/static/images/icons/plus_bold.png
deleted file mode 100644
index 3b489ad..0000000
Binary files a/modules/web_server/static/images/icons/plus_bold.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/save.png b/modules/web_server/static/images/icons/save.png
deleted file mode 100644
index ec688ec..0000000
Binary files a/modules/web_server/static/images/icons/save.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/settings.png b/modules/web_server/static/images/icons/settings.png
deleted file mode 100644
index 0d64cfc..0000000
Binary files a/modules/web_server/static/images/icons/settings.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/success.png b/modules/web_server/static/images/icons/success.png
deleted file mode 100644
index daca0be..0000000
Binary files a/modules/web_server/static/images/icons/success.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/trash.png b/modules/web_server/static/images/icons/trash.png
deleted file mode 100644
index 0cec57b..0000000
Binary files a/modules/web_server/static/images/icons/trash.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/trash_white.png b/modules/web_server/static/images/icons/trash_white.png
deleted file mode 100644
index b2c040c..0000000
Binary files a/modules/web_server/static/images/icons/trash_white.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/types/default.png b/modules/web_server/static/images/icons/types/default.png
deleted file mode 100644
index dd89748..0000000
Binary files a/modules/web_server/static/images/icons/types/default.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/types/inverter.png b/modules/web_server/static/images/icons/types/inverter.png
deleted file mode 100644
index 0b5bfb6..0000000
Binary files a/modules/web_server/static/images/icons/types/inverter.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/warning.png b/modules/web_server/static/images/icons/warning.png
deleted file mode 100644
index 726dc2f..0000000
Binary files a/modules/web_server/static/images/icons/warning.png and /dev/null differ
diff --git a/modules/web_server/static/images/icons/workflow.png b/modules/web_server/static/images/icons/workflow.png
deleted file mode 100644
index f45e3c3..0000000
Binary files a/modules/web_server/static/images/icons/workflow.png and /dev/null differ
diff --git a/modules/web_server/static/images/touch-icon.png b/modules/web_server/static/images/touch-icon.png
deleted file mode 100644
index 569be78..0000000
Binary files a/modules/web_server/static/images/touch-icon.png and /dev/null differ
diff --git a/modules/web_server/static/styles/main.css b/modules/web_server/static/styles/main.css
deleted file mode 100644
index 039b2b6..0000000
--- a/modules/web_server/static/styles/main.css
+++ /dev/null
@@ -1,1163 +0,0 @@
-@font-face {
- font-family: Cantarell;
- font-weight: 200;
- src: url("/static/fonts/Cantarell-Thin.otf") format("opentype");
-}
-@font-face {
- font-family: Cantarell;
- font-weight: 300;
- src: url("/static/fonts/Cantarell-Light.otf") format("opentype");
-}
-@font-face {
- font-family: Cantarell;
- src: url("/static/fonts/Cantarell-Regular.otf") format("opentype");
-}
-@font-face {
- font-family: Cantarell;
- font-weight: bold;
- src: url("/static/fonts/Cantarell-Bold.otf") format("opentype");
-}
-@font-face {
- font-family: Cantarell;
- font-weight: 900;
- src: url("/static/fonts/Cantarell-ExtraBold.otf") format("opentype");
-}
-@font-face {
- font-family: IndieFlower;
- src: url("/static/fonts/IndieFlower-Regular.ttf") format("truetype");
-}
-@font-face {
- font-family: "Roboto Mono";
- src: url("/static/fonts/RobotoMono-Regular.ttf") format("truetype");
-}
-@font-face {
- font-family: "Roboto Mono";
- font-weight: 700;
- src: url("/static/fonts/RobotoMono-Medium.ttf") format("truetype");
-}
-@font-face {
- font-family: "Roboto Mono";
- font-weight: 900;
- src: url("/static/fonts/RobotoMono-Bold.ttf") format("truetype");
-}
-body {
- position: relative;
- height: 100vh;
- min-height: 100vh;
- margin: 0;
- padding-left: 325px;
- background-color: #f1f0f6;
- color: #555;
- font-family: Cantarell, Verdana, sans-serif;
-}
-@media (max-width: 900px) {
- body {
- padding-left: 0;
- }
-}
-
-.block-ladder, .table, .block-list-item, .block-list__add-button, .block-info, .block-small-chart {
- display: inline-block;
- transition: transform ease-in-out 50ms;
- border-radius: 15px;
- background-color: #fff;
- box-shadow: 2px 2px 3px #ddd;
- vertical-align: top;
- box-sizing: border-box;
-}
-
-.workflow-item::before, .block-list-item--white-edge::before {
- position: absolute;
- top: 0;
- right: 0;
- width: 0;
- height: 100%;
- background: #fff;
- box-shadow: 0 0 15px 20px #fff;
- content: "";
-}
-
-.dialog__title--number, .table__cell--handler-id, .block-list-item__title--number {
- display: inline-block;
- position: relative;
- min-width: 20px;
- padding: 0 4px;
- border-radius: 6px;
- background-color: #aaa;
- color: #fff;
- text-align: center;
- vertical-align: top;
- box-sizing: border-box;
-}
-
-a {
- outline: none;
- text-decoration: none;
-}
-a:hover {
- text-decoration: underline;
-}
-
-img {
- image-rendering: -webkit-optimize-contrast;
-}
-
-hr {
- border: 0;
- border-bottom: 1px solid #ccc;
-}
-
-.navbar {
- position: fixed;
- top: 0;
- left: 0;
- width: 325px;
- height: 100%;
- padding: 20px;
- background-color: #f9fafc;
- overflow: auto;
- z-index: 100;
- box-sizing: border-box;
-}
-.navbar__title {
- margin: 0 0 40px;
- padding-left: 10px;
- font-size: 30px;
-}
-.navbar__title--smaller {
- margin-bottom: 0;
- color: #999;
- font-size: 14px;
- letter-spacing: 1px;
- text-transform: uppercase;
-}
-.navbar__logo {
- width: 27px;
- margin-bottom: -3px;
- margin-left: 3px;
- opacity: 0.7;
-}
-.navbar__items {
- margin-top: 8px;
- padding-left: 0;
-}
-@media (max-width: 900px) {
- .navbar {
- display: none;
- width: 100%;
- }
- .navbar--visible {
- display: unset;
- }
-}
-
-.navbar-button {
- display: none;
- position: fixed;
- top: 22px;
- right: 18px;
- width: 38px;
- cursor: pointer;
- opacity: 0.6;
- z-index: 1;
-}
-@media (max-width: 900px) {
- .navbar-button {
- display: block;
- }
-}
-
-.navbar-item {
- display: block;
- height: 70px;
- padding: 10px;
- transition: background-color ease-in-out 200ms;
- border-radius: 10px;
- cursor: pointer;
- box-sizing: border-box;
-}
-.navbar-item:hover {
- box-shadow: 0 0 0 2px inset #f1f0f6;
-}
-.navbar-item--active {
- background-color: #f1f0f6;
-}
-.navbar-item__icon {
- width: 50px;
- margin-right: 10px;
- padding: 8px;
- float: left;
- border-radius: 25%;
- box-sizing: border-box;
-}
-.navbar-item__icon--overview {
- background-color: #7233ff;
-}
-.navbar-item__icon--inspector {
- background-color: #299bec;
-}
-.navbar-item__icon--actions {
- background-color: #65c44c;
-}
-.navbar-item__icon--data {
- background-color: #f5a65b;
-}
-.navbar-item__icon--handlers {
- background-color: #fd8f64;
-}
-.navbar-item__icon--details {
- background-color: #ffcd41;
-}
-.navbar-item__title {
- margin: 4px 0 0;
- font-size: 16px;
- font-weight: 600;
-}
-.navbar-item__description {
- margin: 3px 0 0;
- font-size: 13px;
-}
-
-.notifications__button {
- display: none;
- position: fixed;
- top: 24px;
- right: 26px;
- width: 34px;
- height: 34px;
- border: solid 3px #555;
- border-radius: 20px;
- font-size: 20px;
- font-weight: bold;
- line-height: 27px;
- text-align: center;
- cursor: pointer;
- z-index: 1;
- box-sizing: border-box;
-}
-.notifications__button--active {
- display: unset;
- background: #555;
- color: #f1f0f6;
-}
-@media (max-width: 900px) {
- .notifications__button {
- right: 70px;
- }
-}
-.notifications__wrapper {
- position: fixed;
- top: 0;
- right: -325px;
- width: 325px;
- height: 100%;
- transition: right ease-in-out 200ms;
- background: #f9fafc;
- z-index: 2;
-}
-.notifications__wrapper--visible {
- right: 0;
-}
-.notifications__wrapper--visible .notification-item--active {
- left: 0;
-}
-.notifications__resources {
- display: none;
-}
-
-.notification-item {
- position: relative;
- left: 0;
- margin: 15px;
- padding: 0 10px;
- transition: ease-in-out 200ms;
- transition-property: opacity, left;
- border-radius: 7px;
- background: #d8e6ff;
- color: #35657b;
- opacity: 1;
-}
-.notification-item__icon {
- width: 35px;
- height: 35px;
- margin-top: 15px;
- margin-right: 10px;
- margin-left: 5px;
- float: left;
- background-image: url("/static/images/icons/info_blue.png");
- background-position: center;
- background-size: cover;
- opacity: 0.5;
-}
-.notification-item__icon--error {
- background-image: url("/static/images/icons/ban.png");
-}
-.notification-item__icon--warning {
- background-image: url("/static/images/icons/warning.png");
-}
-.notification-item__icon--success {
- background-image: url("/static/images/icons/success.png");
-}
-.notification-item--error {
- background: #ecc8c5;
- color: #ab1b19;
-}
-.notification-item--warning {
- background: #fbeebd;
- color: #b57c1c;
-}
-.notification-item--success {
- background: #d6f0cb;
- color: #407729;
-}
-.notification-item__title {
- display: block;
- margin-bottom: 3px;
- padding-top: 10px;
- font-size: 16px;
- font-weight: bold;
-}
-.notification-item__description {
- margin-top: 0;
- padding-bottom: 11px;
- padding-left: 50px;
- font-size: 14px;
- line-height: 20px;
-}
-.notification-item__close-button {
- position: absolute;
- top: 15px;
- right: 15px;
- width: 11px;
- height: 11px;
- transition: opacity 200ms ease-in-out;
- background-image: url("/static/images/icons/close.png");
- background-position: center;
- background-size: cover;
- cursor: pointer;
- opacity: 0.1;
-}
-.notification-item__close-button:hover {
- transform: scale(1.1);
-}
-.notification-item:hover .notification-item__close-button {
- opacity: 0.6;
-}
-.notification-item--active {
- left: -325px;
-}
-.notification-item--removed {
- opacity: 0;
-}
-
-.block-info, .block-small-chart {
- width: calc(50% - 20px);
- margin-top: 20px;
- margin-right: 0;
- padding: 20px 25px;
-}
-.block-info:nth-child(2n), .block-small-chart:nth-child(2n) {
- margin-left: 40px;
-}
-.block-info__title, .block-small-chart__title {
- margin-top: 0;
- margin-bottom: 8px;
- font-size: 22px;
-}
-@media (max-width: 1300px) {
- .block-info, .block-small-chart {
- width: 100%;
- }
- .block-info:nth-child(2n), .block-small-chart:nth-child(2n) {
- margin-left: 0;
- }
-}
-
-.block-info-item {
- display: inline-block;
- width: 50%;
- vertical-align: top;
- box-sizing: border-box;
-}
-.block-info-item:nth-child(2n+1) {
- padding-right: 10px;
-}
-.block-info-item:nth-child(2n) {
- padding-left: 10px;
-}
-.block-info-item__title {
- margin-top: 0;
- margin-bottom: 3px;
- padding-top: 15px;
- color: unset;
- font-size: 17px;
- font-weight: bold;
- line-height: 1.2;
-}
-.block-info-item__description {
- margin: 6px 0 0;
- color: #777;
-}
-@media (max-width: 550px) {
- .block-info-item {
- width: 100%;
- }
- .block-info-item:nth-child(n) {
- padding-right: 0;
- padding-left: 0;
- }
-}
-
-.block-list__add-button {
- width: 69px;
- min-width: auto;
- max-width: unset;
- height: 69px;
- margin-top: 20px;
- margin-right: 20px;
- border-radius: 100%;
- background-image: url("/static/images/icons/plus.png");
- background-repeat: no-repeat;
- background-position: center;
- background-size: 60%;
- cursor: pointer;
- opacity: 0.8;
-}
-.block-list__add-button:hover {
- transform: scale(1.03);
-}
-@media (max-width: 550px) {
- .block-list__add-button {
- display: block;
- width: 69px;
- margin: 20px auto;
- }
-}
-
-.block-list-item {
- position: relative;
- min-width: 190px;
- max-width: 280px;
- margin-top: 20px;
- margin-right: 20px;
- padding: 15px;
- cursor: pointer;
- overflow: hidden;
-}
-.block-list-item:hover {
- transform: scale(1.03);
-}
-.block-list-item--green {
- border-left: solid 5px #65c44c;
-}
-.block-list-item--red {
- border-left: solid 5px #ff6f6f;
-}
-.block-list-item--with-icon {
- padding-left: 58px;
-}
-.block-list-item--with-number {
- padding-right: 55px;
-}
-.block-list-item__icon {
- position: absolute;
- left: 13px;
- width: 38px;
- opacity: 0.6;
-}
-.block-list-item__title {
- margin-right: 5px;
- margin-bottom: 0;
- color: #555;
- font-size: 15px;
- font-weight: bolder;
- line-height: 1.2;
- white-space: nowrap;
-}
-.block-list-item__title--number {
- min-width: 20px;
- margin-left: 3px;
- font-size: 12px;
- line-height: 19px;
-}
-.block-list-item__description {
- height: 18px;
- margin-top: 2px;
- margin-bottom: 0;
- font-size: 14px;
- white-space: nowrap;
-}
-.block-list-item__number {
- position: absolute;
- top: 21px;
- right: 17px;
- min-width: 17px;
- padding: 4px;
- border: solid 1px #ccc;
- border-radius: 5px;
- background: #cfffc3;
- font-weight: bold;
- line-height: 17px;
- text-align: center;
-}
-@media (max-width: 550px) {
- .block-list-item {
- width: 100%;
- max-width: unset;
- margin-left: 0;
- }
-}
-
-.button {
- display: inline-block;
- margin-top: 10px;
- margin-right: 10px;
- margin-bottom: 17px;
- padding: 8px 15px;
- border: 0;
- border-radius: 20px;
- background: #fff;
- color: #555;
- font-size: 16px;
- font-weight: bold;
- line-height: 24px;
- box-shadow: 1px 1px 3px #ddd;
- cursor: pointer;
-}
-.button:hover {
- background: #f9fafc;
- box-shadow: 1px 1px 3px #ddd;
-}
-.button__icon {
- width: 17px;
- margin-top: 4px;
- margin-right: 8px;
- opacity: 0.7;
- vertical-align: top;
-}
-.button--small {
- min-width: 50px;
- margin-top: 0;
- margin-bottom: 0;
- padding: 5px 13px;
- border-radius: 5px;
- background: #299bec;
- color: #fff;
- font-size: 14px;
- text-align: center;
-}
-.button--small:hover {
- background: #299bec;
- opacity: 0.9;
-}
-.button--small .button__icon {
- width: 14px;
- margin-top: 5px;
- opacity: 1;
-}
-.button--red {
- background-color: #ff6f6f;
-}
-.button--red:hover {
- background-color: #ff6f6f;
-}
-.button--form {
- display: block;
- margin-bottom: 5px;
- padding: 10px;
- border-radius: 5px;
- background-color: #f8f9ff;
- box-shadow: unset;
- cursor: pointer;
-}
-.button--form:hover {
- background-color: #d8e6ff;
- box-shadow: unset;
-}
-.button--form .button__icon {
- width: 25px;
- margin-top: 0;
- opacity: 0.65;
-}
-
-.table {
- display: inline-table;
- width: 100%;
- margin-top: 20px;
- font-size: 15px;
- border-collapse: collapse;
- overflow: hidden;
-}
-.table__row--header {
- box-shadow: 0 0 8px #eee;
-}
-.table__row:nth-child(2n+3) {
- background: #f8f9ff;
-}
-.table__cell {
- padding: 10px 0;
- text-align: left;
- box-sizing: border-box;
-}
-.table__cell--title {
- padding: 12px 0;
- font-size: 12px;
- font-weight: 800;
- letter-spacing: 1px;
- text-transform: uppercase;
-}
-.table__cell--status {
- width: 140px;
- padding-left: 22px;
- font-size: 12px;
- text-transform: uppercase;
-}
-.table__cell--status-in {
- color: #65c44c;
- font-weight: bold;
-}
-.table__cell--status-out {
- color: #299bec;
- font-weight: bold;
-}
-.table__cell--type {
- width: 80px;
-}
-.table__cell--type-span {
- display: inline-block;
- width: 60px;
- border-radius: 3px;
- background: #299bec;
- color: #fff;
- font-size: 12px;
- font-weight: bold;
- line-height: 23px;
- text-align: center;
- text-transform: uppercase;
-}
-.table__cell--type-json {
- background: #f5a65b;
-}
-.table__cell--type-event {
- background: #7233ff;
-}
-.table__cell--type-text {
- background: #777;
-}
-.table__cell--time {
- width: 90px;
-}
-.table__cell--handler {
- min-width: 250px;
-}
-.table__cell--handler-title {
- font-weight: bold;
-}
-.table__cell--handler-icon {
- width: 30px;
- margin-top: -14px;
- margin-bottom: -10px;
- opacity: 0.6;
-}
-.table__cell--handler-id {
- margin-left: 4px;
- background-color: #ccc;
- font-size: 12px;
- font-weight: bold;
- line-height: 19px;
-}
-.table__cell--data {
- max-width: calc(100vw - 950px);
-}
-.table__cell--data-content {
- position: relative;
- top: -2px;
- font-family: "Roboto Mono", monospace;
- font-size: 12px;
- white-space: nowrap;
-}
-.table__cell--data-content-event {
- top: unset;
- color: #7233ff;
- font-weight: 900;
-}
-.table__cell--data:hover .table__cell--data-content {
- white-space: normal;
-}
-.table__inspect-button {
- width: 20px;
- margin-top: -7px;
- margin-bottom: -5px;
- margin-left: 6px;
- cursor: pointer;
- opacity: 0.6;
- filter: sepia(100%) saturate(100) hue-rotate(-150deg) brightness(1);
-}
-.table__inspect-button:hover {
- opacity: 0.8;
-}
-@media (max-width: 1300px) {
- .table {
- display: block;
- position: relative;
- }
- .table__body {
- display: block;
- }
- .table__row {
- display: block;
- position: relative;
- width: 100%;
- max-width: 100%;
- }
- .table__row--header {
- display: none;
- }
- .table__cell--status {
- width: auto;
- padding-right: 20px;
- }
- .table__cell--handler {
- display: block;
- margin-left: 15px;
- padding: 0;
- }
- .table__cell--data {
- max-width: 100%;
- padding: 20px 20px 10px;
- }
- .table__cell--data-content {
- display: block;
- }
-}
-
-.dialog__container {
- display: flex;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- transition: opacity 0.2s cubic-bezier(0.62, 1.71, 1, 1.64), margin-top 0.2s step-start;
- transition-timing-function: cubic-bezier(0.62, 1.71, 1, 1.64), steps(1, start);
- background-color: rgba(0, 0, 0, 0.3);
- z-index: 100;
-}
-.dialog__container--hidden {
- margin-top: -1000%;
- transition-timing-function: cubic-bezier(0.62, 1.71, 1, 1.64), step-end;
- opacity: 0;
-}
-.dialog__container--hidden .dialog__window {
- transform: scale(0);
- opacity: 0;
-}
-.dialog__window {
- position: relative;
- width: 600px;
- min-height: 400px;
- max-height: calc(100% - 100px);
- padding: 25px 30px;
- transition: 0.2s cubic-bezier(0.5, -0.7, 0.6, 1);
- border: 0;
- border-radius: 20px;
- background-color: #fff;
- color: #555;
- overflow: auto;
- box-sizing: border-box;
-}
-.dialog__title {
- margin-top: 0;
- color: #555;
- font-size: 22px;
- font-weight: bold;
-}
-.dialog__title--number {
- top: 3px;
- min-width: 22px;
- margin-left: 3px;
- padding: 0 7px 0 8px;
- font-size: 14px;
- line-height: 22px;
-}
-
-.checkbox-list-item {
- display: block;
- margin-bottom: 5px;
- padding: 10px;
- border-radius: 5px;
- background-color: #f8f9ff;
- cursor: pointer;
-}
-.checkbox-list-item:hover {
- box-shadow: inset 0 0 0 2px #d8e6ff;
-}
-.checkbox-list-item__input {
- width: 20px;
- height: 20px;
- margin-right: 12px;
-}
-.checkbox-list-item--checked {
- background-color: #d8e6ff;
- box-shadow: unset;
-}
-.checkbox-list-item .checkbox-list-item__data, .checkbox-list-item .checkbox-list-item__text {
- position: relative;
- top: 3px;
- vertical-align: super;
-}
-.checkbox-list-item__data {
- margin-right: 10px;
- float: right;
- color: #299bec;
-}
-
-.form-group {
- margin-bottom: 20px;
-}
-.form-group__title {
- display: block;
- margin-bottom: 5px;
- color: #299bec;
- font-size: 13px;
- font-weight: bold;
- text-transform: uppercase;
- opacity: 0.7;
-}
-.form-group__textarea {
- min-height: 50px;
- max-height: 150px;
- resize: vertical;
-}
-
-.input-text {
- width: 100%;
- padding: 7px 10px;
- border: solid 2px #d8e6ff;
- border-radius: 5px;
- outline: none;
- background-color: #fff;
- color: #555;
- font-size: 16px;
- box-sizing: border-box;
-}
-.input-text:focus {
- border-color: #299bec;
- background-color: #f8f9ff;
- color: #444;
-}
-
-.block-ladder {
- position: relative;
- flex: 0 0 calc(25% - 19px);
- max-height: 280px;
- margin-top: 20px;
- margin-right: 25px;
- padding-bottom: 15px;
- padding-left: 20px;
- overflow: auto;
-}
-.block-ladder:nth-child(4n) {
- margin-right: 0;
-}
-.block-ladder__title {
- margin-right: 50px;
-}
-.block-ladder__corner-button {
- display: block;
- position: absolute;
- top: 19px;
- right: 20px;
- width: 25px;
- cursor: pointer;
- opacity: 0.6;
-}
-@media (max-width: 1300px) {
- .block-ladder {
- flex: 0 0 calc(50% - 13px);
- }
- .block-ladder:nth-child(2n) {
- margin-right: 0;
- }
-}
-@media (max-width: 550px) {
- .block-ladder {
- flex: 0 0 100%;
- max-height: unset;
- margin-right: 0;
- }
-}
-
-.block-ladder-item {
- display: inline-block;
- width: calc(100% - 20px);
- margin-bottom: 6px;
- padding: 5px 8px;
- border: 0;
- border-radius: 10px;
- font-size: 14px;
- font-weight: bold;
- text-align: left;
- cursor: pointer;
- opacity: 0.5;
-}
-.block-ladder-item:hover {
- background: #d8e6ff;
- color: #444;
- opacity: 1;
-}
-
-.workflow-item {
- display: inline-block;
- position: relative;
- max-width: 250px;
- height: 51px;
- margin: 15px 5px 0;
- padding: 2px 20px 0;
- border-radius: 10px;
- background: #fff;
- text-align: center;
- box-shadow: 2px 2px 3px #ddd;
- cursor: pointer;
- overflow: hidden;
- vertical-align: middle;
-}
-.workflow-item:hover .workflow-item__arrow {
- display: flex;
-}
-.workflow-item__arrow {
- display: none;
- position: absolute;
- top: 0;
- width: 28px;
- height: 100%;
- cursor: pointer;
- overflow: hidden;
-}
-.workflow-item__arrow img {
- height: 15px;
- margin: auto;
-}
-.workflow-item__arrow:hover img {
- filter: brightness(0.8);
-}
-.workflow-item__arrow--left {
- left: 0;
-}
-.workflow-item__arrow--right {
- right: 0;
-}
-.workflow-item__arrow--right img {
- transform: scaleX(-1);
-}
-.workflow-item--active {
- border: solid 2px #65c44c;
-}
-.workflow-item__log-index {
- display: inline-block;
- min-width: 12px;
- height: 21px;
- margin-right: 5px;
- padding: 0 5px 0 6px;
- border-radius: 11px;
- background: #65c44c;
- color: #fff;
- line-height: 19px;
- text-align: center;
- vertical-align: middle;
-}
-.workflow-item__name {
- margin: 4px 7px 2px;
- font-weight: bold;
-}
-.workflow-item__description {
- margin: 1px 7px 0;
- font-size: 13px;
- white-space: nowrap;
-}
-
-.workflow {
- position: relative;
- padding-right: 40px;
- padding-bottom: 15px;
- border-bottom: solid 1px #ddd;
-}
-.workflow:last-child {
- border-bottom: unset;
-}
-.workflow .workflow__delete-item, .workflow .workflow__add-item, .workflow .workflow__id {
- display: inline-block;
- width: 40px;
- height: 40px;
- margin-top: 15px;
- margin-left: 5px;
- padding: 5px;
- border-radius: 10px;
- vertical-align: middle;
- box-sizing: border-box;
-}
-.workflow__id {
- margin-top: 15px;
- margin-right: 5px;
- border: solid 1px #ccc;
- font-size: 18px;
- font-weight: bold;
- line-height: 27px;
- text-align: center;
-}
-.workflow__add-item {
- border: solid 1px #ccc;
- background-color: #fff;
- cursor: pointer;
- opacity: 0.5;
-}
-.workflow__add-item:hover {
- opacity: 0.9;
-}
-.workflow__delete-item {
- position: absolute;
- top: 5px;
- right: 0;
- cursor: pointer;
- opacity: 0.5;
-}
-.workflow__delete-item:hover {
- opacity: 0.9;
-}
-
-.block-small-chart {
- min-height: 210px;
- cursor: pointer;
-}
-.block-small-chart__chart {
- max-height: 100px;
- margin-top: 30px;
- margin-bottom: 10px;
-}
-
-.inspector-chart-view {
- position: fixed;
- top: 0;
- left: 0;
- width: calc(100% - 50px);
- height: calc(100% - 50px);
- margin: 25px;
- transition: 0.4s cubic-bezier(0.5, -0.7, 0.6, 1);
- border-radius: 20px;
- background: #fff;
- box-shadow: 0 0 0 7000px rgba(0, 0, 0, 0.1);
- z-index: 150;
-}
-@media (max-width: 900px) {
- .inspector-chart-view {
- width: 100%;
- height: 100%;
- margin: 0;
- border-radius: 0;
- box-shadow: none;
- }
-}
-.inspector-chart-view--hidden {
- margin-top: -1000%;
- transform: scale(0);
- transition-timing-function: cubic-bezier(0.62, 1.71, 1, 1.64), step-end;
- opacity: 0;
-}
-.inspector-chart-view__close-button {
- position: absolute;
- top: 15px;
- right: 25px;
- width: 25px;
- cursor: pointer;
- opacity: 0.6;
-}
-.inspector-chart-view__close-button:hover {
- opacity: 1;
-}
-.inspector-chart-view__settings-button {
- position: absolute;
- top: 15px;
- left: 25px;
- width: 25px;
- cursor: pointer;
- opacity: 0.6;
-}
-.inspector-chart-view__settings-button:hover {
- opacity: 1;
-}
-.inspector-chart-view__chart {
- height: 100%;
-}
-
-.inspector-config-panel {
- display: none;
- position: absolute;
- width: 330px;
- max-height: 100%;
- padding: 30px 20px 15px;
- transition: ease-in-out 100ms;
- border-radius: 20px;
- background: #fff;
- box-shadow: 0 0 5px #ccc;
- overflow: auto;
- box-sizing: border-box;
-}
-@media (max-width: 900px) {
- .inspector-config-panel {
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- padding: 40px 20px 20px;
- border-radius: 0;
- }
-}
-.inspector-config-panel__close-button {
- position: absolute;
- top: 15px;
- right: 25px;
- width: 20px;
- cursor: pointer;
- opacity: 0.6;
-}
-.inspector-config-panel__close-button:hover {
- opacity: 1;
-}
-@media (max-width: 900px) {
- .inspector-config-panel__close-button {
- width: 25px;
- }
-}
-.inspector-config-panel__section-title {
- font-size: 20px;
-}
-
-.content-container {
- height: 100%;
- padding: 20px 35px;
- overflow: auto;
- box-sizing: border-box;
-}
-.content-container__title {
- margin: 7px 0;
- font-size: 22px;
-}
-.content-container__title--with-margin {
- margin-top: 30px;
-}
-.content-container__smaller-title {
- margin-bottom: 0;
- font-size: 17px;
-}
-@media (max-width: 900px) {
- .content-container {
- padding: 20px;
- }
-}
-
-.flex-wrap-container {
- display: flex;
- flex-wrap: wrap;
-}
-
-/*# sourceMappingURL=main.css.map */
diff --git a/modules/web_server/static/styles/main.css.map b/modules/web_server/static/styles/main.css.map
deleted file mode 100644
index 106577f..0000000
--- a/modules/web_server/static/styles/main.css.map
+++ /dev/null
@@ -1 +0,0 @@
-{"version":3,"sourceRoot":"","sources":["../../templates/components/fonts.sass","../../templates/components/defaults.sass","../../templates/components/variables.sass","../../templates/components/navbar/navbar.sass","../../templates/components/navbar/navbar_button.sass","../../templates/components/navbar/navbar_item/navbar_item.sass","../../templates/components/notifications/notifications.sass","../../templates/components/notifications/notification_item/notification_item.sass","../../templates/components/block_info/block_info.sass","../../templates/components/block_info/block_info_item/block_info_item.sass","../../templates/components/block_list/block_list.sass","../../templates/components/block_list/block_list_item/block_list_item.sass","../../templates/components/button/button.sass","../../templates/components/table/table.sass","../../templates/components/dialog/dialog.sass","../../templates/components/checkbox_list/checkbox_list_item/checkbox_list_item.sass","../../templates/components/form/form.sass","../../templates/components/input_text/input_text.sass","../../templates/components/block_ladder/block_ladder.sass","../../templates/components/block_ladder/block_ladder_item/block_ladder_item.sass","../../templates/components/workflow/workflow_item/workflow_item.sass","../../templates/components/workflow/workflow.sass","../../templates/components/block_small_chart/block_small_chart.sass","../../templates/components/inspector-chart-view/inspector-chart-view.sass","../../templates/components/main.sass"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;;AAEF;EACE;EACA;EACA;;AAEF;EACE;EACA;;AAEF;EACE;EACA;EACA;;AAEF;EACE;EACA;EACA;;AAEF;EACE;EACA;;AAEF;EACE;EACA;;AAEF;EACE;EACA;EACA;;AAEF;EACE;EACA;EACA;;ACpCF;EACE;EACA;EACA;EACA;EACA;EACA,kBCNc;EDOd,OCGc;EDFd,aD8BM;;AC5BN;EAVF;IAWI;;;;AAGJ;EACE;EACA;EACA;EACA,kBCfc;EDgBd;EACA;EACA;;;AAGA;EACE;EACA;EACA;EACA;EACA;EACA,YC3BY;ED4BZ;EACA;;;AAEJ;EACE;EACA;EACA;EACA;EACA;EACA,kBCjCc;EDkCd,OCtCc;EDuCd;EACA;EACA;;;AAEF;EACE;EACA;;AAEA;EACE;;;AAEJ;EACE;;;AAEF;EACE;EACA;;;AE9DF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,kBDLc;ECMd;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA,ODPU;ECQV;EACA;EACA;;AAEJ;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;;AAEF;EAlCF;IAmCI;IACA;;EAEA;IACE;;;;ACvCN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAVF;IAWI;;;;ACXJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE,kBHTY;;AGWd;EACE;EACA;EACA;EACA;EACA;EACA;;AAGE;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;AADF;EACE;;AAGN;EACE;EACA;EACA;;AAEF;EACE;EACA;;;AClCF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA,YJNU;EIOV,OJjBU;;AImBZ;EAtBF;IAuBI;;;AAGJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YJhCY;EIiCZ;;AAEA;EACE;;AAEA;EACE;;AAEN;EACE;;;AC5CJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YLwBc;EKvBd,OLwBc;EKvBd;;AAIA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE;;AAEF;EACE;;AAEJ;EACE,YLFY;EKGZ,OLFY;;AKId;EACE,YLJY;EKKZ,OLJY;;AKMd;EACE,YLNY;EKOZ,OLNY;;AKQd;EACE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAEJ;EACE;;AAEF;EACE;;;ACrFJ;EAEE;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE;EACA;EACA;;AAEF;EAfF;IAgBI;;EAEA;IACE;;;;ACnBN;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA,OPVY;;AOYd;EAzBF;IA0BI;;EAEA;IACE;IACA;;;;AC7BJ;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EAnBF;IAoBI;IACA;IACA;;;;ACvBN;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA,OTrBY;ESsBZ;EACA;EACA;EACA;;AAEA;EAEE;EACA;EACA;EACA;;AAEJ;EACE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YT1DY;ES2DZ;EACA;EACA;;AAKF;EAvEF;IAwEI;IACA;IACA;;;;AC1EJ;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YVFc;EUGd,OVIc;EUHd;EACA;EACA;EACA;EAEA;;AAEA;EACE,YVjBY;EUkBZ;;AAEF;EACE;EACA;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA,YVdY;EUeZ,OV7BY;EU8BZ;EACA;;AAEA;EACE,YVpBU;EUqBV;;AAEF;EACE;EACA;EACA;;AAEJ;EACE,kBVzBY;;AU2BZ;EACE,kBV5BU;;AU8Bd;EACE;EACA;EACA;EACA;EACA,kBVzDY;EU0DZ;EACA;;AAEA;EACE,kBVjCU;EUkCV;;AAEF;EACE;EACA;EACA;;;ACvEN;EAEE;EACA;EACA;EACA;EACA;EACA;;AAIE;EACE;;AAEF;EACE,YXZU;;AWcd;EACE;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;EACA;;AAEA;EACE,OXhBQ;EWiBR;;AAEF;EACE,OXrBQ;EWsBR;;AAEJ;EACE;;AAEA;EACE;EACA;EACA;EACA,YX/BQ;EWgCR,OX9CQ;EW+CR;EACA;EACA;EACA;EACA;;AAEF;EACE,YXtCQ;;AWwCV;EACE,YX5CQ;;AW8CV;EACE,YXtDQ;;AWwDZ;EACE;;AAEF;EACE;;AAEA;EACE;;AAEF;EACE;EACA;EACA;EACA;;AAEF;EAEE;EACA,kBX7EQ;EW8ER;EACA;EACA;;AAEJ;EAEE;;AAEA;EACE;EACA;EACA,abxDA;EayDA;EACA;;AAEA;EACE;EACA,OXrFM;EWsFN;;AAGF;EACE;;AAER;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEJ;EA5HF;IA6HI;IACA;;EAEA;IACE;;EAEF;IACE;IACA;IACA;IACA;;EAEA;IACE;;EAGF;IACE;IACA;;EAEF;IACE;IACA;IACA;;EAEF;IACE;IACA;;EAEA;IACE;;;;ACxJR;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,kBZMY;EYLZ;;AAEA;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAEN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,kBZ1BY;EY2BZ,OZpBY;EYqBZ;EACA;;AAEF;EACE;EACA,OZ1BY;EY2BZ;EACA;;AAEA;EAEE;EACA;EACA;EACA;EACA;EACA;;;ACnDN;EAEE;EACA;EACA;EACA;EACA,kBbHc;EaId;;AAEA;EACE;;AAEF;EAEE,OADO;EAEP,QAFO;EAGP;;AAEF;EACE,kBbaY;EaZZ;;AAEF;EACE;EACA;EACA;;AAKF;EAEE;EACA;EACA,ObbY;;;AcrBhB;EACE;;AAEA;EACE;EACA;EACA,OdeY;EcdZ;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;;;ACfJ;EACE;EACA;EACA;EACA;EACA;EACA,kBfCc;EeAd,OfOc;EeNd;EACA;;AAEA;EACE,cfSY;EeRZ,kBfVY;EeWZ,OfCY;;;AgBfhB;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;EA1BF;IA2BI;;EAEA;IACE;;;AAEJ;EAhCF;IAiCI;IACA;IACA;;;;ACnCJ;EACE;EACA;EACA;EACA;EAEA;EAEA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE,YjBgBY;EiBfZ,OjBFY;EiBGZ;;;AClBJ;EAGE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YlBHc;EkBId;EACA;EACA;EACA;EAEA;;AAGE;EACE;;AAEJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGA;EACE;;AAEJ;EACE;;AAEF;EACE;;AAEA;EACE;;AAEN;EACE;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA,YlBpCY;EkBqCZ,OlBpDY;EkBqDZ;EACA;EACA;;AAEF;EACE;EACA;;AAEF;EACE;EACA;EACA;;;ACrEJ;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;EAEE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEF;EAEE;EACA,kBnB5BY;EmB6BZ;EACA;;AAEA;EACE;;AAEJ;EAEE;EACA;EACA;EACA;EACA;;AAEA;EACE;;;ACnDN;EAEE;EACA;;AAKA;EACE;EACA;EACA;;;ACXJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YrBFc;EqBGd;EACA;;AAEA;EAbF;IAcI;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA;EACA;EACA;;AAEF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEJ;EACE;;;AAEJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA,YrBrDc;EqBsDd;EACA;EACA;;AAEA;EAbF;IAcI;IACA;IACA;IACA;IACA;IACA;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEF;EAXF;IAYI;;;AAGJ;EACE;;;ACxEJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAEA;EACE;;AAEJ;EACE;EACA;;AAEF;EAjBF;IAkBI;;;;AAGJ;EACE;EACA","file":"main.css"}
\ No newline at end of file
diff --git a/modules/web_server/templates/components/block_info/block_info.html b/modules/web_server/templates/components/block_info/block_info.html
deleted file mode 100644
index 11c27b7..0000000
--- a/modules/web_server/templates/components/block_info/block_info.html
+++ /dev/null
@@ -1,12 +0,0 @@
-{% from "components/block_info/block_info_item/block_info_item.html" import block_info_item %}
-
-{% macro info_block(title, data) -%}
-
-
{{ title }}
-
- {%- for item in data -%}
- {{ block_info_item(item, data[item]) }}
- {%- endfor -%}
-
-
-{%- endmacro %}
diff --git a/modules/web_server/templates/components/block_info/block_info.sass b/modules/web_server/templates/components/block_info/block_info.sass
deleted file mode 100644
index 642cfc8..0000000
--- a/modules/web_server/templates/components/block_info/block_info.sass
+++ /dev/null
@@ -1,22 +0,0 @@
-.block-info
- @extend %tile
- width: calc(50% - 20px)
- margin-top: 20px
- margin-right: 0
- padding: 20px 25px
-
- &:nth-child(2n)
- margin-left: 40px
-
- &__title
- margin-top: 0
- margin-bottom: 8px
- font-size: 22px
-
- @media (max-width: 1300px)
- width: 100%
-
- &:nth-child(2n)
- margin-left: 0
-
-@import 'block_info_item/block_info_item'
diff --git a/modules/web_server/templates/components/block_info/block_info_item/block_info_item.html b/modules/web_server/templates/components/block_info/block_info_item/block_info_item.html
deleted file mode 100644
index 7ceb7b7..0000000
--- a/modules/web_server/templates/components/block_info/block_info_item/block_info_item.html
+++ /dev/null
@@ -1,6 +0,0 @@
-{%- macro block_info_item(title, description) -%}
-
-
{{ title }}
-
{{ description }}
-
-{%- endmacro -%}
diff --git a/modules/web_server/templates/components/block_info/block_info_item/block_info_item.sass b/modules/web_server/templates/components/block_info/block_info_item/block_info_item.sass
deleted file mode 100644
index eae639c..0000000
--- a/modules/web_server/templates/components/block_info/block_info_item/block_info_item.sass
+++ /dev/null
@@ -1,31 +0,0 @@
-.block-info-item
- display: inline-block
- width: 50%
- vertical-align: top
- box-sizing: border-box
-
- &:nth-child(2n+1)
- padding-right: 10px
-
- &:nth-child(2n)
- padding-left: 10px
-
- &__title
- margin-top: 0
- margin-bottom: 3px
- padding-top: 15px
- color: unset
- font-size: 17px
- font-weight: bold
- line-height: 1.2
-
- &__description
- margin: 6px 0 0
- color: $grey-777
-
- @media (max-width: 550px)
- width: 100%
-
- &:nth-child(n)
- padding-right: 0
- padding-left: 0
diff --git a/modules/web_server/templates/components/block_ladder/block_ladder.sass b/modules/web_server/templates/components/block_ladder/block_ladder.sass
deleted file mode 100644
index 6a39fce..0000000
--- a/modules/web_server/templates/components/block_ladder/block_ladder.sass
+++ /dev/null
@@ -1,39 +0,0 @@
-.block-ladder
- @extend %tile
- position: relative
- flex: 0 0 calc(25% - 19px)
- max-height: 280px
- margin-top: 20px
- margin-right: 25px
- padding-bottom: 15px
- padding-left: 20px
- overflow: auto
-
- &:nth-child(4n)
- margin-right: 0
-
- &__title
- margin-right: 50px
-
- &__corner-button
- display: block
- position: absolute
- top: 19px
- right: 20px
- width: 25px
- cursor: pointer
- opacity: .6
-
- @media (max-width: 1300px)
- flex: 0 0 calc(50% - 13px)
-
- &:nth-child(2n)
- margin-right: 0
-
- @media (max-width: 550px)
- flex: 0 0 100%
- max-height: unset
- margin-right: 0
-
-
-@import 'block_ladder_item/block_ladder_item'
diff --git a/modules/web_server/templates/components/block_ladder/block_ladder_item/block_ladder_item.sass b/modules/web_server/templates/components/block_ladder/block_ladder_item/block_ladder_item.sass
deleted file mode 100644
index 1277c67..0000000
--- a/modules/web_server/templates/components/block_ladder/block_ladder_item/block_ladder_item.sass
+++ /dev/null
@@ -1,19 +0,0 @@
-.block-ladder-item
- display: inline-block
- width: calc(100% - 20px)
- margin-bottom: 6px
- padding: 5px 8px
- //border: solid 1px $info-fg
- border: 0
- //border-left: solid 3px $info-fg
- border-radius: 10px
- font-size: 14px
- font-weight: bold
- text-align: left
- cursor: pointer
- opacity: .5
-
- &:hover
- background: $info-bg
- color: $grey-444
- opacity: 1
diff --git a/modules/web_server/templates/components/block_list/block_list.sass b/modules/web_server/templates/components/block_list/block_list.sass
deleted file mode 100644
index 60deecf..0000000
--- a/modules/web_server/templates/components/block_list/block_list.sass
+++ /dev/null
@@ -1,26 +0,0 @@
-.block-list
- &__add-button
- @extend %tile
- width: 69px
- min-width: auto
- max-width: unset
- height: 69px
- margin-top: 20px
- margin-right: 20px
- border-radius: 100%
- background-image: url('/static/images/icons/plus.png')
- background-repeat: no-repeat
- background-position: center
- background-size: 60%
- cursor: pointer
- opacity: .8
-
- &:hover
- transform: scale(1.03)
-
- @media (max-width: 550px)
- display: block
- width: 69px
- margin: 20px auto
-
-@import 'block_list_item/block_list_item'
diff --git a/modules/web_server/templates/components/block_list/block_list_item/block_list_item.html b/modules/web_server/templates/components/block_list/block_list_item/block_list_item.html
deleted file mode 100644
index cecea94..0000000
--- a/modules/web_server/templates/components/block_list/block_list_item/block_list_item.html
+++ /dev/null
@@ -1,24 +0,0 @@
-{%- macro block_list_item(title, description="", icon="", index="", modifier="", onclick="", number="") -%}
-
-
- {% if icon %}
-
- {% endif %}
-
-
{{ title }}{% if index %} {{ index }} {% endif %}
-
-
- {{ description }}
-
-
- {% if number %}
-
{{ number }}
- {% endif %}
-
-
-{%- endmacro -%}
diff --git a/modules/web_server/templates/components/block_list/block_list_item/block_list_item.sass b/modules/web_server/templates/components/block_list/block_list_item/block_list_item.sass
deleted file mode 100644
index 89eec6d..0000000
--- a/modules/web_server/templates/components/block_list/block_list_item/block_list_item.sass
+++ /dev/null
@@ -1,75 +0,0 @@
-.block-list-item
- @extend %tile
- position: relative
- min-width: 190px
- max-width: 280px
- margin-top: 20px
- margin-right: 20px
- padding: 15px
- cursor: pointer
- overflow: hidden
-
- &:hover
- transform: scale(1.03)
-
- &--green
- border-left: solid 5px $green
-
- &--red
- border-left: solid 5px $red
-
- &--with-icon
- padding-left: 58px
-
- &--with-number
- padding-right: 55px
-
- &__icon
- position: absolute
- left: 13px
- width: 38px
- opacity: .6
-
- &__title
- margin-right: 5px
- margin-bottom: 0
- color: $grey-555
- font-size: 15px
- font-weight: bolder
- line-height: 1.2
- white-space: nowrap
-
- &--number
- @extend %small-id
- min-width: 20px
- margin-left: 3px
- font-size: 12px
- line-height: 19px
-
- &__description
- height: 18px
- margin-top: 2px
- margin-bottom: 0
- font-size: 14px
- white-space: nowrap
-
- &__number
- position: absolute
- top: 21px
- right: 17px
- min-width: 17px
- padding: 4px
- border: solid 1px $silver-ccc
- border-radius: 5px
- background: $green-light
- font-weight: bold
- line-height: 17px
- text-align: center
-
- &--white-edge
- @extend %white-edge
-
- @media (max-width: 550px)
- width: 100%
- max-width: unset
- margin-left: 0
diff --git a/modules/web_server/templates/components/block_small_chart/block_small_chart.sass b/modules/web_server/templates/components/block_small_chart/block_small_chart.sass
deleted file mode 100644
index 3093be2..0000000
--- a/modules/web_server/templates/components/block_small_chart/block_small_chart.sass
+++ /dev/null
@@ -1,12 +0,0 @@
-.block-small-chart
- @extend .block-info
- min-height: 210px
- cursor: pointer
-
- &__title
- @extend .block-info__title
-
- &__chart
- max-height: 100px
- margin-top: 30px
- margin-bottom: 10px
diff --git a/modules/web_server/templates/components/button/button.html b/modules/web_server/templates/components/button/button.html
deleted file mode 100644
index 70893dd..0000000
--- a/modules/web_server/templates/components/button/button.html
+++ /dev/null
@@ -1,8 +0,0 @@
-{% macro button(text, onclick="", icon="", modifiers=[]) -%}
-
-{%- endmacro %}
diff --git a/modules/web_server/templates/components/button/button.sass b/modules/web_server/templates/components/button/button.sass
deleted file mode 100644
index ad25f3c..0000000
--- a/modules/web_server/templates/components/button/button.sass
+++ /dev/null
@@ -1,72 +0,0 @@
-.button
- $button: &
- display: inline-block
- margin-top: 10px
- margin-right: 10px
- margin-bottom: 17px
- padding: 8px 15px
- border: 0
- border-radius: 20px
- background: $white-fff
- color: $grey-555
- font-size: 16px
- font-weight: bold
- line-height: 24px
- box-shadow: 1px 1px 3px $shadow
-
- cursor: pointer
-
- &:hover
- background: $white-main
- box-shadow: 1px 1px 3px $shadow
-
- &__icon
- width: 17px
- margin-top: 4px
- margin-right: 8px
- opacity: .7
- vertical-align: top
-
- &--small
- min-width: 50px
- margin-top: 0
- margin-bottom: 0
- padding: 5px 13px
- border-radius: 5px
- background: $blue
- color: $white-fff
- font-size: 14px
- text-align: center
-
- &:hover
- background: $blue
- opacity: .9
-
- #{$button}__icon
- width: 14px
- margin-top: 5px
- opacity: 1
-
- &--red
- background-color: $red
-
- &:hover
- background-color: $red
-
- &--form
- display: block
- margin-bottom: 5px
- padding: 10px
- border-radius: 5px
- background-color: $white-blue
- box-shadow: unset
- cursor: pointer
-
- &:hover
- background-color: $info-bg
- box-shadow: unset
-
- #{$button}__icon
- width: 25px
- margin-top: 0
- opacity: .65
diff --git a/modules/web_server/templates/components/checkbox_list/checkbox_list.sass b/modules/web_server/templates/components/checkbox_list/checkbox_list.sass
deleted file mode 100644
index 13ca7da..0000000
--- a/modules/web_server/templates/components/checkbox_list/checkbox_list.sass
+++ /dev/null
@@ -1 +0,0 @@
-@import 'checkbox_list_item/checkbox_list_item'
diff --git a/modules/web_server/templates/components/checkbox_list/checkbox_list_item/checkbox_list_item.html b/modules/web_server/templates/components/checkbox_list/checkbox_list_item/checkbox_list_item.html
deleted file mode 100644
index 709f15c..0000000
--- a/modules/web_server/templates/components/checkbox_list/checkbox_list_item/checkbox_list_item.html
+++ /dev/null
@@ -1,21 +0,0 @@
-{%- macro checkbox_list_item(attribute, title="", checked="", data="", name="") -%}
-
- {% if title %}{{ title }}{% else %}{{ attribute }}{% endif %}
- {% if data != '' %}
- {{ data }}
- {% endif %}
-
-{%- endmacro -%}
diff --git a/modules/web_server/templates/components/checkbox_list/checkbox_list_item/checkbox_list_item.sass b/modules/web_server/templates/components/checkbox_list/checkbox_list_item/checkbox_list_item.sass
deleted file mode 100644
index d3d8ec0..0000000
--- a/modules/web_server/templates/components/checkbox_list/checkbox_list_item/checkbox_list_item.sass
+++ /dev/null
@@ -1,35 +0,0 @@
-.checkbox-list-item
- $this: &
- display: block
- margin-bottom: 5px
- padding: 10px
- border-radius: 5px
- background-color: $white-blue
- cursor: pointer
-
- &:hover
- box-shadow: inset 0 0 0 2px $info-bg
-
- &__input
- $size: 20px
- width: $size
- height: $size
- margin-right: 12px
-
- &--checked
- background-color: $info-bg
- box-shadow: unset
-
- %text
- position: relative
- top: 3px
- vertical-align: super
-
- &__text
- @extend %text
-
- &__data
- @extend %text
- margin-right: 10px
- float: right
- color: $blue
diff --git a/modules/web_server/templates/components/defaults.sass b/modules/web_server/templates/components/defaults.sass
deleted file mode 100644
index e95f82e..0000000
--- a/modules/web_server/templates/components/defaults.sass
+++ /dev/null
@@ -1,63 +0,0 @@
-@import 'fonts'
-@import 'variables'
-
-
-body
- position: relative
- height: 100vh
- min-height: 100vh
- margin: 0
- padding-left: 325px
- background-color: $white-bg
- color: $grey-555
- font-family: $font1
-
- @media (max-width: 900px)
- padding-left: 0
-
-
-%tile
- display: inline-block
- transition: transform ease-in-out 50ms
- border-radius: 15px
- background-color: $white-fff
- box-shadow: 2px 2px 3px $shadow
- vertical-align: top
- box-sizing: border-box
-
-%white-edge
- &::before
- position: absolute
- top: 0
- right: 0
- width: 0
- height: 100%
- background: $white-fff
- box-shadow: 0 0 15px 20px $white-fff
- content: ''
-
-%small-id
- display: inline-block
- position: relative
- min-width: 20px
- padding: 0 4px
- border-radius: 6px
- background-color: $silver-aaa
- color: $white-fff
- text-align: center
- vertical-align: top
- box-sizing: border-box
-
-a
- outline: none
- text-decoration: none
-
- &:hover
- text-decoration: underline
-
-img
- image-rendering: -webkit-optimize-contrast
-
-hr
- border: 0
- border-bottom: 1px solid $silver-ccc
diff --git a/modules/web_server/templates/components/dialog/dialog.html b/modules/web_server/templates/components/dialog/dialog.html
deleted file mode 100644
index e6f9116..0000000
--- a/modules/web_server/templates/components/dialog/dialog.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
- {% block dialog_title %}{% endblock %}
-
-
diff --git a/modules/web_server/templates/components/dialog/dialog.sass b/modules/web_server/templates/components/dialog/dialog.sass
deleted file mode 100644
index ceb4a40..0000000
--- a/modules/web_server/templates/components/dialog/dialog.sass
+++ /dev/null
@@ -1,52 +0,0 @@
-.dialog
- $this: &
-
- &__container
- display: flex
- position: fixed
- top: 0
- left: 0
- width: 100%
- height: 100%
- transition: opacity .2s cubic-bezier(.62, 1.71, 1, 1.64), margin-top .2s step-start
- transition-timing-function: cubic-bezier(.62, 1.71, 1, 1.64), steps(1, start)
- background-color: $black-03
- z-index: 100
-
- &--hidden
- margin-top: -1000%
- transition-timing-function: cubic-bezier(.62, 1.71, 1, 1.64), step-end
- opacity: 0
-
- #{$this}__window
- transform: scale(0)
- opacity: 0
-
- &__window
- position: relative
- width: 600px
- min-height: 400px
- max-height: calc(100% - 100px)
- padding: 25px 30px
- transition: .2s cubic-bezier(.5, -.7, .6, 1)
- border: 0
- border-radius: 20px
- background-color: $white-fff
- color: $grey-555
- overflow: auto
- box-sizing: border-box
-
- &__title
- margin-top: 0
- color: $grey-555
- font-size: 22px
- font-weight: bold
-
- &--number
- @extend %small-id
- top: 3px
- min-width: 22px
- margin-left: 3px
- padding: 0 7px 0 8px
- font-size: 14px
- line-height: 22px
diff --git a/modules/web_server/templates/components/dialog/dialog_container.html b/modules/web_server/templates/components/dialog/dialog_container.html
deleted file mode 100644
index b944a66..0000000
--- a/modules/web_server/templates/components/dialog/dialog_container.html
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- {% include "components/dialog/dialog.html" %}
-
-
diff --git a/modules/web_server/templates/components/fonts.sass b/modules/web_server/templates/components/fonts.sass
deleted file mode 100644
index 0a10b3f..0000000
--- a/modules/web_server/templates/components/fonts.sass
+++ /dev/null
@@ -1,44 +0,0 @@
-@font-face
- font-family: Cantarell
- font-weight: 200
- src: url('/static/fonts/Cantarell-Thin.otf') format('opentype')
-
-@font-face
- font-family: Cantarell
- font-weight: 300
- src: url('/static/fonts/Cantarell-Light.otf') format('opentype')
-
-@font-face
- font-family: Cantarell
- src: url('/static/fonts/Cantarell-Regular.otf') format('opentype')
-
-@font-face
- font-family: Cantarell
- font-weight: bold
- src: url('/static/fonts/Cantarell-Bold.otf') format('opentype')
-
-@font-face
- font-family: Cantarell
- font-weight: 900
- src: url('/static/fonts/Cantarell-ExtraBold.otf') format('opentype')
-
-@font-face
- font-family: IndieFlower
- src: url('/static/fonts/IndieFlower-Regular.ttf') format('truetype')
-
-@font-face
- font-family: 'Roboto Mono'
- src: url('/static/fonts/RobotoMono-Regular.ttf') format('truetype')
-
-@font-face
- font-family: 'Roboto Mono'
- font-weight: 700
- src: url('/static/fonts/RobotoMono-Medium.ttf') format('truetype')
-
-@font-face
- font-family: 'Roboto Mono'
- font-weight: 900
- src: url('/static/fonts/RobotoMono-Bold.ttf') format('truetype')
-
-$font1: Cantarell, Verdana, sans-serif
-$font2: 'Roboto Mono', monospace
diff --git a/modules/web_server/templates/components/form/form.sass b/modules/web_server/templates/components/form/form.sass
deleted file mode 100644
index 070daa7..0000000
--- a/modules/web_server/templates/components/form/form.sass
+++ /dev/null
@@ -1,16 +0,0 @@
-.form-group
- margin-bottom: 20px
-
- &__title
- display: block
- margin-bottom: 5px
- color: $blue
- font-size: 13px
- font-weight: bold
- text-transform: uppercase
- opacity: .7
-
- &__textarea
- min-height: 50px
- max-height: 150px
- resize: vertical
diff --git a/modules/web_server/templates/components/input_text/input_text.sass b/modules/web_server/templates/components/input_text/input_text.sass
deleted file mode 100644
index 8f8d745..0000000
--- a/modules/web_server/templates/components/input_text/input_text.sass
+++ /dev/null
@@ -1,15 +0,0 @@
-.input-text
- width: 100%
- padding: 7px 10px
- border: solid 2px $info-bg
- border-radius: 5px
- outline: none
- background-color: $white-fff
- color: $grey-555
- font-size: 16px
- box-sizing: border-box
-
- &:focus
- border-color: $blue
- background-color: $white-blue
- color: $grey-444
diff --git a/modules/web_server/templates/components/inspector-chart-view/inspector-chart-view.sass b/modules/web_server/templates/components/inspector-chart-view/inspector-chart-view.sass
deleted file mode 100644
index 7fa28ff..0000000
--- a/modules/web_server/templates/components/inspector-chart-view/inspector-chart-view.sass
+++ /dev/null
@@ -1,91 +0,0 @@
-.inspector-chart-view
- position: fixed
- top: 0
- left: 0
- width: calc(100% - 50px)
- height: calc(100% - 50px)
- margin: 25px
- transition: .4s cubic-bezier(.5, -.7, .6, 1)
- border-radius: 20px
- background: $white-fff
- box-shadow: 0 0 0 7000px $black-01
- z-index: 150
-
- @media (max-width: 900px)
- width: 100%
- height: 100%
- margin: 0
- border-radius: 0
- box-shadow: none
-
-
- &--hidden
- margin-top: -1000%
- transform: scale(0)
- transition-timing-function: cubic-bezier(.62, 1.71, 1, 1.64), step-end
- opacity: 0
-
- &__close-button
- position: absolute
- top: 15px
- right: 25px
- width: 25px
- cursor: pointer
- opacity: .6
-
- &:hover
- opacity: 1
-
- &__settings-button
- position: absolute
- top: 15px
- left: 25px
- width: 25px
- cursor: pointer
- opacity: .6
-
- &:hover
- opacity: 1
-
- &__chart
- height: 100%
-
-.inspector-config-panel
- display: none
- position: absolute
- width: 330px
- max-height: 100%
- padding: 30px 20px 15px
- transition: ease-in-out 100ms
- border-radius: 20px
- background: $white-fff
- box-shadow: 0 0 5px $silver-ccc
- overflow: auto
- box-sizing: border-box
-
- @media (max-width: 900px)
- top: 0
- left: 0
- width: 100%
- height: 100%
- padding: 40px 20px 20px
- border-radius: 0
-
-
- &__close-button
- position: absolute
- top: 15px
- right: 25px
- width: 20px
- cursor: pointer
- opacity: .6
-
- &:hover
- opacity: 1
-
- @media (max-width: 900px)
- width: 25px
-
-
- &__section-title
- font-size: 20px
diff --git a/modules/web_server/templates/components/main.sass b/modules/web_server/templates/components/main.sass
deleted file mode 100644
index f2e0a68..0000000
--- a/modules/web_server/templates/components/main.sass
+++ /dev/null
@@ -1,42 +0,0 @@
-@import 'defaults'
-
-@import 'navbar/navbar'
-@import 'notifications/notifications'
-@import 'block_info/block_info'
-@import 'block_list/block_list'
-@import 'button/button'
-@import 'table/table'
-@import 'dialog/dialog'
-@import 'checkbox_list/checkbox_list'
-@import 'form/form'
-@import 'input_text/input_text'
-@import 'block_ladder/block_ladder'
-@import 'workflow/workflow'
-@import 'block_small_chart/block_small_chart'
-@import 'inspector-chart-view/inspector-chart-view'
-
-
-.content-container
- height: 100%
- padding: 20px 35px
- overflow: auto
- box-sizing: border-box
-
- &__title
- margin: 7px 0
- font-size: 22px
-
- &--with-margin
- margin-top: 30px
-
- &__smaller-title
- margin-bottom: 0
- font-size: 17px
-
- @media (max-width: 900px)
- padding: 20px
-
-
-.flex-wrap-container
- display: flex
- flex-wrap: wrap
diff --git a/modules/web_server/templates/components/navbar/navbar.html b/modules/web_server/templates/components/navbar/navbar.html
deleted file mode 100644
index 74592ce..0000000
--- a/modules/web_server/templates/components/navbar/navbar.html
+++ /dev/null
@@ -1,21 +0,0 @@
-{% from 'components/navbar/navbar_item/navbar_item.html' import navbar_item %}
-
-
-
-
\ No newline at end of file
diff --git a/modules/web_server/templates/components/navbar/navbar.sass b/modules/web_server/templates/components/navbar/navbar.sass
deleted file mode 100644
index bba8143..0000000
--- a/modules/web_server/templates/components/navbar/navbar.sass
+++ /dev/null
@@ -1,43 +0,0 @@
-.navbar
- position: fixed
- top: 0
- left: 0
- width: 325px
- height: 100%
- padding: 20px
- background-color: $white-main
- overflow: auto
- z-index: 100
- box-sizing: border-box
-
- &__title
- margin: 0 0 40px
- padding-left: 10px
- font-size: 30px
-
- &--smaller
- margin-bottom: 0
- color: $silver-999
- font-size: 14px
- letter-spacing: 1px
- text-transform: uppercase
-
- &__logo
- width: 27px
- margin-bottom: -3px
- margin-left: 3px
- opacity: .7
-
- &__items
- margin-top: 8px
- padding-left: 0
-
- @media (max-width: 900px)
- display: none
- width: 100%
-
- &--visible
- display: unset
-
-@import 'navbar_button'
-@import 'navbar_item/navbar_item'
diff --git a/modules/web_server/templates/components/navbar/navbar_button.sass b/modules/web_server/templates/components/navbar/navbar_button.sass
deleted file mode 100644
index 8c089d0..0000000
--- a/modules/web_server/templates/components/navbar/navbar_button.sass
+++ /dev/null
@@ -1,12 +0,0 @@
-.navbar-button
- display: none
- position: fixed
- top: 22px
- right: 18px
- width: 38px
- cursor: pointer
- opacity: .6
- z-index: 1
-
- @media (max-width: 900px)
- display: block
diff --git a/modules/web_server/templates/components/navbar/navbar_item/navbar_item.html b/modules/web_server/templates/components/navbar/navbar_item/navbar_item.html
deleted file mode 100644
index 3a9e1ad..0000000
--- a/modules/web_server/templates/components/navbar/navbar_item/navbar_item.html
+++ /dev/null
@@ -1,7 +0,0 @@
-{% macro navbar_item(title, description, id) -%}
-
-{%- endmacro %}
diff --git a/modules/web_server/templates/components/navbar/navbar_item/navbar_item.sass b/modules/web_server/templates/components/navbar/navbar_item/navbar_item.sass
deleted file mode 100644
index 5ac8e71..0000000
--- a/modules/web_server/templates/components/navbar/navbar_item/navbar_item.sass
+++ /dev/null
@@ -1,36 +0,0 @@
-.navbar-item
- display: block
- height: 70px
- padding: 10px
- transition: background-color ease-in-out 200ms
- border-radius: 10px
- cursor: pointer
- box-sizing: border-box
-
- &:hover
- box-shadow: 0 0 0 2px inset $white-bg
-
- &--active
- background-color: $white-bg
-
- &__icon
- width: 50px
- margin-right: 10px
- padding: 8px
- float: left
- border-radius: 25%
- box-sizing: border-box
-
- @each $bind in $bindings
- &--#{nth($bind, 1)}
- background-color: #{nth($bind, 2)}
-
-
- &__title
- margin: 4px 0 0
- font-size: 16px
- font-weight: 600
-
- &__description
- margin: 3px 0 0
- font-size: 13px
diff --git a/modules/web_server/templates/components/notifications/notification_item/notification_item.sass b/modules/web_server/templates/components/notifications/notification_item/notification_item.sass
deleted file mode 100644
index 514427d..0000000
--- a/modules/web_server/templates/components/notifications/notification_item/notification_item.sass
+++ /dev/null
@@ -1,86 +0,0 @@
-.notification-item
- position: relative
- left: 0
- margin: 15px
- padding: 0 10px
- transition: ease-in-out 200ms
- transition-property: opacity, left
- border-radius: 7px
- background: $info-bg
- color: $info-fg
- opacity: 1
-
- $this: &
-
- &__icon
- width: 35px
- height: 35px
- margin-top: 15px
- margin-right: 10px
- margin-left: 5px
- float: left
- background-image: url('/static/images/icons/info_blue.png')
- background-position: center
- background-size: cover
- opacity: .5
-
- &--error
- background-image: url('/static/images/icons/ban.png')
-
- &--warning
- background-image: url('/static/images/icons/warning.png')
-
- &--success
- background-image: url('/static/images/icons/success.png')
-
- &--error
- background: $error-bg
- color: $error-fg
-
- &--warning
- background: $warning-bg
- color: $warning-fg
-
- &--success
- background: $success-bg
- color: $success-fg
-
- &__title
- display: block
- margin-bottom: 3px
- padding-top: 10px
- font-size: 16px
- font-weight: bold
-
- &__description
- margin-top: 0
- padding-bottom: 11px
- padding-left: 50px
- font-size: 14px
- line-height: 20px
-
- &__close-button
- position: absolute
- top: 15px
- right: 15px
- width: 11px
- height: 11px
- transition: opacity 200ms ease-in-out
- background-image: url('/static/images/icons/close.png')
- background-position: center
- background-size: cover
- cursor: pointer
- opacity: .1
-
- &:hover
- transform: scale(1.1)
-
- &:hover
- #{$this}__close-button
- opacity: .6
-
- &--active
- left: -325px
-
- &--removed
- opacity: 0
diff --git a/modules/web_server/templates/components/notifications/notifications.html b/modules/web_server/templates/components/notifications/notifications.html
deleted file mode 100644
index 99a71b0..0000000
--- a/modules/web_server/templates/components/notifications/notifications.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
diff --git a/modules/web_server/templates/components/notifications/notifications.sass b/modules/web_server/templates/components/notifications/notifications.sass
deleted file mode 100644
index 66c15e3..0000000
--- a/modules/web_server/templates/components/notifications/notifications.sass
+++ /dev/null
@@ -1,47 +0,0 @@
-.notifications
- &__button
- display: none
- position: fixed
- top: 24px
- right: 26px
- width: 34px
- height: 34px
- border: solid 3px $grey-555
- border-radius: 20px
- font-size: 20px
- font-weight: bold
- line-height: 27px
- text-align: center
- cursor: pointer
- z-index: 1
- box-sizing: border-box
-
- &--active
- display: unset
- background: $grey-555
- color: $white-bg
-
- @media (max-width: 900px)
- right: 70px
-
-
- &__wrapper
- position: fixed
- top: 0
- right: -325px
- width: 325px
- height: 100%
- transition: right ease-in-out 200ms
- background: $white-main
- z-index: 2
-
- &--visible
- right: 0
-
- .notification-item--active
- left: 0
-
- &__resources
- display: none
-
-@import 'notification_item/notification_item'
diff --git a/modules/web_server/templates/components/table/table.sass b/modules/web_server/templates/components/table/table.sass
deleted file mode 100644
index 1b4deb6..0000000
--- a/modules/web_server/templates/components/table/table.sass
+++ /dev/null
@@ -1,156 +0,0 @@
-.table
- @extend %tile
- display: inline-table
- width: 100%
- margin-top: 20px
- font-size: 15px
- border-collapse: collapse
- overflow: hidden
-
- &__row
-
- &--header
- box-shadow: 0 0 8px $white-eee
-
- &:nth-child(2n+3)
- background: $white-blue
-
- &__cell
- padding: 10px 0
- text-align: left
- box-sizing: border-box
-
- &--title
- padding: 12px 0
- font-size: 12px
- font-weight: 800
- letter-spacing: 1px
- text-transform: uppercase
-
- // Custom elements
-
- &--status
- width: 140px
- padding-left: 22px
- font-size: 12px
- text-transform: uppercase
-
- &-in
- color: $green
- font-weight: bold
-
- &-out
- color: $blue
- font-weight: bold
-
- &--type
- width: 80px
-
- &-span
- display: inline-block
- width: 60px
- border-radius: 3px
- background: $blue
- color: $white-fff
- font-size: 12px
- font-weight: bold
- line-height: 23px
- text-align: center
- text-transform: uppercase
-
- &-json
- background: $orange
-
- &-event
- background: $purple
-
- &-text
- background: $grey-777
-
- &--time
- width: 90px
-
- &--handler
- min-width: 250px
-
- &-title
- font-weight: bold
-
- &-icon
- width: 30px
- margin-top: -14px
- margin-bottom: -10px
- opacity: .6
-
- &-id
- @extend %small-id
- margin-left: 4px
- background-color: $silver-ccc
- font-size: 12px
- font-weight: bold
- line-height: 19px
-
- &--data
- $data: &
- max-width: calc(100vw - 950px)
-
- &-content
- position: relative
- top: -2px
- font-family: $font2
- font-size: 12px
- white-space: nowrap
-
- &-event
- top: unset
- color: $purple
- font-weight: 900
-
- &:hover
- #{$data}-content
- white-space: normal
-
- &__inspect-button
- width: 20px
- margin-top: -7px
- margin-bottom: -5px
- margin-left: 6px
- cursor: pointer
- opacity: .6
- filter: sepia(100%) saturate(100) hue-rotate(-150deg) brightness(1)
-
- &:hover
- opacity: .8
-
- @media (max-width: 1300px)
- display: block
- position: relative
-
- &__body
- display: block
-
- &__row
- display: block
- position: relative
- width: 100%
- max-width: 100%
-
- &--header
- display: none
-
- &__cell
- &--status
- width: auto
- padding-right: 20px
-
- &--handler
- display: block
- margin-left: 15px
- padding: 0
-
- &--data
- max-width: 100%
- padding: 20px 20px 10px
-
- &-content
- display: block
diff --git a/modules/web_server/templates/components/variables.sass b/modules/web_server/templates/components/variables.sass
deleted file mode 100644
index 36b5945..0000000
--- a/modules/web_server/templates/components/variables.sass
+++ /dev/null
@@ -1,43 +0,0 @@
-// Colors schemes
-
-$white-main: #f9fafc
-$white-blue: #f8f9ff
-$white-bg: #f1f0f6
-$green-light: #cfffc3
-
-$white-fff: #fff
-$white-eee: #eee
-$white-ddd: #ddd
-$silver-ccc: #ccc
-$silver-aaa: #aaa
-$silver-999: #999
-$grey-777: #777
-$grey-555: #555
-$grey-444: #444
-
-$black-01: rgba(0, 0, 0, .1)
-$black-03: rgba(0, 0, 0, .3)
-
-$purple: #7233ff
-$blue: #299bec
-$green: #65c44c
-$orange: #f5a65b
-$salmon: #fd8f64
-$red: #ff6f6f
-$yellow: #ffcd41
-
-$shadow: $white-ddd
-
-// Notifications
-
-$info-bg: #d8e6ff
-$info-fg: #35657b
-$error-bg: #ecc8c5
-$error-fg: #ab1b19
-$warning-bg: #fbeebd
-$warning-fg: #b57c1c
-$success-bg: #d6f0cb
-$success-fg: #407729
-
-
-$bindings: [['overview', $purple], ['inspector', $blue], ['actions', $green], ['data', $orange], ['handlers', $salmon], ['details' $yellow]]
diff --git a/modules/web_server/templates/components/workflow/workflow.sass b/modules/web_server/templates/components/workflow/workflow.sass
deleted file mode 100644
index 86ab0b6..0000000
--- a/modules/web_server/templates/components/workflow/workflow.sass
+++ /dev/null
@@ -1,52 +0,0 @@
-@import 'workflow_item/workflow_item'
-
-.workflow
- position: relative
- padding-right: 40px
- padding-bottom: 15px
- border-bottom: solid 1px $white-ddd
-
- &:last-child
- border-bottom: unset
-
- %box
- display: inline-block
- width: 40px
- height: 40px
- margin-top: 15px
- margin-left: 5px
- padding: 5px
- border-radius: 10px
- vertical-align: middle
- box-sizing: border-box
-
- &__id
- @extend %box
- margin-top: 15px
- margin-right: 5px
- border: solid 1px $silver-ccc
- font-size: 18px
- font-weight: bold
- line-height: 27px
- text-align: center
-
- &__add-item
- @extend %box
- border: solid 1px $silver-ccc
- background-color: $white-fff
- cursor: pointer
- opacity: .5
-
- &:hover
- opacity: .9
-
- &__delete-item
- @extend %box
- position: absolute
- top: 5px
- right: 0
- cursor: pointer
- opacity: .5
-
- &:hover
- opacity: .9
diff --git a/modules/web_server/templates/components/workflow/workflow_item/workflow_item.sass b/modules/web_server/templates/components/workflow/workflow_item/workflow_item.sass
deleted file mode 100644
index e388de1..0000000
--- a/modules/web_server/templates/components/workflow/workflow_item/workflow_item.sass
+++ /dev/null
@@ -1,72 +0,0 @@
-.workflow-item
- @extend %white-edge
- $this: &
- display: inline-block
- position: relative
- max-width: 250px
- height: 51px
- margin: 15px 5px 0
- padding: 2px 20px 0
- border-radius: 10px
- background: $white-fff
- text-align: center
- box-shadow: 2px 2px 3px $white-ddd
- cursor: pointer
- overflow: hidden
-
- vertical-align: middle
-
- &:hover
- #{$this}__arrow
- display: flex
-
- &__arrow
- display: none
- position: absolute
- top: 0
- width: 28px
- height: 100%
- cursor: pointer
- overflow: hidden
-
- img
- height: 15px
- margin: auto
-
- &:hover
- img
- filter: brightness(.8)
-
- &--left
- left: 0
-
- &--right
- right: 0
-
- img
- transform: scaleX(-1)
-
- &--active
- border: solid 2px $green
-
- &__log-index
- display: inline-block
- min-width: 12px
- height: 21px
- margin-right: 5px
- padding: 0 5px 0 6px
- border-radius: 11px
- background: $green
- color: $white-fff
- line-height: 19px
- text-align: center
- vertical-align: middle
-
- &__name
- margin: 4px 7px 2px
- font-weight: bold
-
- &__description
- margin: 1px 7px 0
- font-size: 13px
- white-space: nowrap
diff --git a/modules/web_server/templates/dialogs/add_new_event_listener.html b/modules/web_server/templates/dialogs/add_new_event_listener.html
deleted file mode 100644
index 53df83e..0000000
--- a/modules/web_server/templates/dialogs/add_new_event_listener.html
+++ /dev/null
@@ -1,40 +0,0 @@
-{% extends "components/dialog/dialog.html" %}
-
-{% from "components/button/button.html" import button %}
-{% from "components/checkbox_list/checkbox_list_item/checkbox_list_item.html" import checkbox_list_item %}
-
-{% block dialog_title %}
-Add new event listener
-{% endblock %}
-
-{% block dialog_form %}
-
- Target handler
-
- {% for handler in handlers %}
- {{ handlers[handler].get_name() }} (id:{{ handler }})
- {% endfor %}
-
-
-
-
- Listen for event label or attribute name
-
-
-
-
- Execute workflow
-
- None
- {% for workflow in workflows %}
- {{ workflow.id }}
- {% endfor %}
-
-
-
-
-{{ checkbox_list_item("data_listener", "Listen for JSON attributes instead of events") }}
-
-
-{{ button("Save", "window.app.dialog.send('/add_new_event_listener')", "save.png", ["small"]) }}
-{% endblock %}
diff --git a/modules/web_server/templates/dialogs/add_new_handler.html b/modules/web_server/templates/dialogs/add_new_handler.html
deleted file mode 100644
index dacedf9..0000000
--- a/modules/web_server/templates/dialogs/add_new_handler.html
+++ /dev/null
@@ -1,36 +0,0 @@
-{% extends "components/dialog/dialog.html" %}
-
-{% from "components/button/button.html" import button %}
-{% from "components/checkbox_list/checkbox_list_item/checkbox_list_item.html" import checkbox_list_item %}
-
-{% block dialog_title %}
-Add new {{ handler.name }}
-{% endblock %}
-
-{% block dialog_form %}
-
-
-
- Handler label
-
-
-
-{% for field in fields %}
-
- {% if fields[field].0 == "string" %}
- {{ fields[field].1 }}
-
- {% elif fields[field].0 == "int" %}
- {{ fields[field].1 }}
-
- {% elif fields[field].0 == "float" %}
- {{ fields[field].1 }}
-
- {% elif fields[field].0 == "bool" %}
- {{ checkbox_list_item(field, fields[field].1, fields[field].2, "", "_" + field + "_") }}
- {% endif %}
-
-{% endfor %}
-
-{{ button("Save", "window.app.dialog.send('/add_new_handler')", "save.png", ["small"]) }}
-{% endblock %}
diff --git a/modules/web_server/templates/dialogs/add_new_routine.html b/modules/web_server/templates/dialogs/add_new_routine.html
deleted file mode 100644
index d94e7ed..0000000
--- a/modules/web_server/templates/dialogs/add_new_routine.html
+++ /dev/null
@@ -1,53 +0,0 @@
-{% extends "components/dialog/dialog.html" %}
-
-{% from "components/button/button.html" import button %}
-{% from "components/checkbox_list/checkbox_list_item/checkbox_list_item.html" import checkbox_list_item %}
-
-{% block dialog_title %}
-{{ routine.name }} - New routine
-{% endblock %}
-
-{% block dialog_form %}
-
-
-
-{% for field in routine.config_fields %}
-
- {% if routine.config_fields[field].0 == "string" %}
- {{ routine.config_fields[field].1 }}
-
- {% elif routine.config_fields[field].0 == "int" %}
- {{ routine.config_fields[field].1 }}
-
- {% elif routine.config_fields[field].0 == "float" %}
- {{ routine.config_fields[field].1 }}
-
- {% elif routine.config_fields[field].0 == "bool" %}
- {{ checkbox_list_item(field, routine.config_fields[field].1, routine.config_fields[field].2, "", "_" + field + "_") }}
- {% elif routine.config_fields[field].0 == "handlerInstance" %}
- {{ routine.config_fields[field].1 }}
-
- {% for handler in handlers %}
- {{ handlers[handler].get_name() }} (id:{{ handler }})
- {% endfor %}
-
- {% elif routine.config_fields[field].0 == "workflowInstance" %}
- {{ routine.config_fields[field].1 }}
-
- None
- {% for workflow in workflows %}
- Workflow {{ workflow.id }}
- {% endfor %}
-
- {% elif routine.config_fields[field].0 == "condition" %}
- {{ routine.config_fields[field].1 }}
-
- {% elif routine.config_fields[field].0 == "configuration" %}
- {{ routine.config_fields[field].1 }}
-
- {% endif %}
-
-{% endfor %}
-
-{{ button("Save", "window.app.dialog.send('/add_new_routine')", "save.png", ["small"]) }}
-{% endblock %}
diff --git a/modules/web_server/templates/dialogs/choose_handler_type.html b/modules/web_server/templates/dialogs/choose_handler_type.html
deleted file mode 100644
index 0d90189..0000000
--- a/modules/web_server/templates/dialogs/choose_handler_type.html
+++ /dev/null
@@ -1,27 +0,0 @@
-{% extends "components/dialog/dialog.html" %}
-
-{% from "components/button/button.html" import button %}
-
-{% block dialog_title %}
-Choose handler type
-{% endblock %}
-
-{% block dialog_form %}
-
-{% endblock %}
diff --git a/modules/web_server/templates/dialogs/choose_routine_type.html b/modules/web_server/templates/dialogs/choose_routine_type.html
deleted file mode 100644
index 8f8da61..0000000
--- a/modules/web_server/templates/dialogs/choose_routine_type.html
+++ /dev/null
@@ -1,30 +0,0 @@
-{% extends "components/dialog/dialog.html" %}
-
-{% from "components/button/button.html" import button %}
-
-{% block dialog_title %}
-Choose new routine
-{% endblock %}
-
-{% block dialog_form %}
-
-{% endblock %}
diff --git a/modules/web_server/templates/dialogs/edit_event_listener.html b/modules/web_server/templates/dialogs/edit_event_listener.html
deleted file mode 100644
index 1c78a0b..0000000
--- a/modules/web_server/templates/dialogs/edit_event_listener.html
+++ /dev/null
@@ -1,41 +0,0 @@
-{% extends "components/dialog/dialog.html" %}
-
-{% from "components/button/button.html" import button %}
-{% from "components/checkbox_list/checkbox_list_item/checkbox_list_item.html" import checkbox_list_item %}
-
-{% block dialog_title %}
-Edit event listener
-{% endblock %}
-
-{% block dialog_form %}
-
- Target handler
-
- {% for handler in handlers %}
- {{ handlers[handler].get_name() }} (id:{{ handler }})
- {% endfor %}
-
-
-
-
- Listen for event label or attribute name
-
-
-
-
- Execute workflow
-
- None
- {% for workflow in workflows %}
- {{ workflow.id }}
- {% endfor %}
-
-
-
-
-{{ checkbox_list_item("data_listener", "Listen for JSON attributes instead of events", listener.get_data_listener_status(), "", "") }}
-
-
-{{ button("Save changes", "window.app.dialog.send('/edit_event_listener/" + listener.id|string + "')", "save.png", ["small"]) }}
-{{ button("Delete listener", "window.app.dialog.send('/delete_event_listener/" + listener.id|string + "')", "trash_white.png", ["small", "red"]) }}
-{% endblock %}
diff --git a/modules/web_server/templates/dialogs/edit_handler.html b/modules/web_server/templates/dialogs/edit_handler.html
deleted file mode 100644
index 1e0f402..0000000
--- a/modules/web_server/templates/dialogs/edit_handler.html
+++ /dev/null
@@ -1,37 +0,0 @@
-{% extends "components/dialog/dialog.html" %}
-
-{% from "components/button/button.html" import button %}
-{% from "components/checkbox_list/checkbox_list_item/checkbox_list_item.html" import checkbox_list_item %}
-
-{% block dialog_title %}
-{{ handler.get_name() }} {{ id }}
-{% endblock %}
-
-{% block dialog_form %}
-
-
-
- Handler label
-
-
-
-{% for field in handler.config_fields %}
-
- {% if handler.config_fields[field].0 == "string" %}
- {{ handler.config_fields[field].1 }}
-
- {% elif handler.config_fields[field].0 == "int" %}
- {{ handler.config_fields[field].1 }}
-
- {% elif handler.config_fields[field].0 == "float" %}
- {{ handler.config_fields[field].1 }}
-
- {% elif handler.config_fields[field].0 == "bool" %}
- {{ checkbox_list_item(field, handler.config_fields[field].1, handler.get_config()[field], "", "_" + field + "_") }}
- {% endif %}
-
-{% endfor %}
-
-{{ button("Save changes", "window.app.dialog.send('/edit_handler/" + id|string + "')", "save.png", ["small"]) }}
-{{ button("Delete handler", "window.app.dialog.send('/delete_handler/" + id|string + "')", "trash_white.png", ["small", "red"]) }}
-{% endblock %}
diff --git a/modules/web_server/templates/dialogs/edit_routine.html b/modules/web_server/templates/dialogs/edit_routine.html
deleted file mode 100644
index 98d7186..0000000
--- a/modules/web_server/templates/dialogs/edit_routine.html
+++ /dev/null
@@ -1,51 +0,0 @@
-{% extends "components/dialog/dialog.html" %}
-
-{% from "components/button/button.html" import button %}
-{% from "components/checkbox_list/checkbox_list_item/checkbox_list_item.html" import checkbox_list_item %}
-
-{% block dialog_title %}
-{{ routine.get_name() }} {{ routine.id }}
-{% endblock %}
-
-{% block dialog_form %}
-{% for field in routine.config_fields %}
-
- {% if routine.config_fields[field].0 == "string" %}
- {{ routine.config_fields[field].1 }}
-
- {% elif routine.config_fields[field].0 == "int" %}
- {{ routine.config_fields[field].1 }}
-
- {% elif routine.config_fields[field].0 == "float" %}
- {{ routine.config_fields[field].1 }}
-
- {% elif routine.config_fields[field].0 == "bool" %}
- {{ checkbox_list_item(field, routine.config_fields[field].1, routine.get_config()[field], "", "_" + field + "_") }}
- {% elif routine.config_fields[field].0 == "handlerInstance" %}
- {{ routine.config_fields[field].1 }}
-
- {% for handler in handlers %}
- {{ handlers[handler].get_name() }} (id:{{ handler }})
- {% endfor %}
-
- {% elif routine.config_fields[field].0 == "workflowInstance" %}
- {{ routine.config_fields[field].1 }}
-
- None
- {% for workflow in workflows %}
- Workflow {{ workflow.id }}
- {% endfor %}
-
- {% elif routine.config_fields[field].0 == "condition" %}
- {{ routine.config_fields[field].1 }}
-
- {% elif routine.config_fields[field].0 == "configuration" %}
- {{ routine.config_fields[field].1 }}
-
- {% endif %}
-
-{% endfor %}
-
-{{ button("Save changes", "window.app.dialog.send('/edit_routine/" + routine.id|string + "')", "save.png", ["small"]) }}
-{{ button("Delete routine", "window.app.dialog.send('/delete_routine/" + routine.id|string + "')", "trash_white.png", ["small", "red"]) }}
-{% endblock %}
diff --git a/modules/web_server/templates/dialogs/json_attributes_to_store.html b/modules/web_server/templates/dialogs/json_attributes_to_store.html
deleted file mode 100644
index 2fef1b2..0000000
--- a/modules/web_server/templates/dialogs/json_attributes_to_store.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "components/dialog/dialog.html" %}
-
-{% from "components/button/button.html" import button %}
-{% from "components/checkbox_list/checkbox_list_item/checkbox_list_item.html" import checkbox_list_item %}
-
-{% block dialog_title %}
-{{ handler.get_name() }} {{ id }}
-{% endblock %}
-
-{% block dialog_form %}
-
-{{ button("Save", "window.app.dialog.send('/edit_json_attributes_to_store/" + id|string + "')", "save.png", ["small"]) }}
-{% endblock %}
diff --git a/modules/web_server/templates/layouts/default.html b/modules/web_server/templates/layouts/default.html
deleted file mode 100644
index ab3e250..0000000
--- a/modules/web_server/templates/layouts/default.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-{% with url="" %}
-
- {% block title %}ContWatch{% endblock %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{% include "components/navbar/navbar.html" %}
-{% include "components/notifications/notifications.html" %}
-
- {% include "components/dialog/dialog_container.html" %}
-
-
-
-
-{% endwith %}
-
diff --git a/modules/web_server/templates/pages/actions.html b/modules/web_server/templates/pages/actions.html
deleted file mode 100644
index f429e86..0000000
--- a/modules/web_server/templates/pages/actions.html
+++ /dev/null
@@ -1,61 +0,0 @@
-{% from 'components/block_list/block_list_item/block_list_item.html' import block_list_item %}
-{% from 'components/button/button.html' import button %}
-
-Event listeners
-
-
-
- {%- for listener in manager.event_manager.event_listeners -%}
- {{ block_list_item(
- manager.get_handler(listener.get_handler_id()).get_name(),
- listener.label,
- "types/" + manager.get_handler(listener.get_handler_id()).icon + ".png",
- listener.get_handler_id(),
- '',
- "window.app.dialog.load('edit_event_listener', {'listener_id': '" + listener.id|string + "'})",
- listener.workflow.id
- ) }}
- {%- endfor -%}
-
-
-
-
Workflows
- {% if manager.event_manager.workflows %}
-
- {% for workflow in manager.event_manager.workflows %}
-
-
{{ workflow }}
- {% for routine in manager.event_manager.workflows[workflow].routines %}
-
- {% if loop.index != 1 %}
-
-
-
- {% endif %}
-
{% for log in routine_log %}{% if log[0] == routine.id %}{{ loop.index }} {% endif %}{% endfor %}{{ routine.get_name() }}
-
{{ routine.get_description() }}
- {% if loop.index != manager.event_manager.workflows[workflow].routines | length %}
-
-
-
- {% endif %}
-
- {% endfor %}
-
-
-
- {% endfor %}
-
- {% endif %}
- {{ button("New workflow", "window.app.actions.createNewWorkflow(0)", "plus_bold.png") }}
-
diff --git a/modules/web_server/templates/pages/data.html b/modules/web_server/templates/pages/data.html
deleted file mode 100644
index 58effb2..0000000
--- a/modules/web_server/templates/pages/data.html
+++ /dev/null
@@ -1,29 +0,0 @@
-Data stored in the database
-
-{#
#}
-{# #}
-{# Display ghost data (?) #}
-{#
#}
- {% if not manager.registered_handlers %}
-
Nothing to display
- {% endif %}
- {%- for handler in manager.registered_handlers -%}
-
-
{{ manager.registered_handlers[handler].get_name() }}
-
-
- {% if (site_config["ghost_data"] and not attributes[handler] and not ghost_attributes[handler]) or (not site_config["ghost_data"] and not attributes[handler]) %}
-
There is nothing to display.
- {% else %}
- {% for attribute in attributes[handler] %}
-
{{ attribute }}
- {% endfor %}
- {% if site_config["ghost_data"] %}
- {% for attribute in ghost_attributes[handler] %}
-
{{ attribute }}
- {% endfor %}
- {% endif %}
- {% endif %}
-
- {%- endfor -%}
-
diff --git a/modules/web_server/templates/pages/details.html b/modules/web_server/templates/pages/details.html
deleted file mode 100644
index a6960c6..0000000
--- a/modules/web_server/templates/pages/details.html
+++ /dev/null
@@ -1,14 +0,0 @@
-{% from 'components/block_info/block_info.html' import info_block %}
-{% from 'components/button/button.html' import button %}
-
-Advanced information
-
- {%- for category in data -%}
- {{ info_block(category, data[category]) }}
- {%- endfor -%}
-
-
-Options
-
- {{ button("Clear all database tables", "window.app.deleteAllTables()", "trash.png") }}
-
diff --git a/modules/web_server/templates/pages/handlers.html b/modules/web_server/templates/pages/handlers.html
deleted file mode 100644
index 4c3016e..0000000
--- a/modules/web_server/templates/pages/handlers.html
+++ /dev/null
@@ -1,16 +0,0 @@
-{% from 'components/block_list/block_list_item/block_list_item.html' import block_list_item %}
-
-List of configured handlers
-
- {%- for handler in handlers -%}
- {{ block_list_item(
- handlers[handler].get_name(),
- handlers[handler].get_description(),
- "types/" + handlers[handler].icon + ".png",
- handler,
- 'green' if handlers[handler].is_connected() else 'red',
- "window.app.dialog.load('edit_handler', {'handler_id': '" + handler|string + "'})"
- ) }}
- {%- endfor -%}
-
-
diff --git a/modules/web_server/templates/pages/inspector.html b/modules/web_server/templates/pages/inspector.html
deleted file mode 100644
index cbc0e27..0000000
--- a/modules/web_server/templates/pages/inspector.html
+++ /dev/null
@@ -1,118 +0,0 @@
-{% from 'components/button/button.html' import button %}
-
-Inspector
-
- {{ button("New chart", "window.app.inspector.openEmpty()", "plus_bold.png") }}
- {% if views %}
-
Saved views
- {% endif %}
- {% for view in views -%}
-
-
{{ view.label }}
-
-
- {%- endfor %}
-
-
-
-
-
-
-
-
-
- Select date
-
-
-
Chart data
- {% for handler in manager.get_handlers() %}
-
- {% if manager.get_handlers()[handler].get_storage_attributes() %}
-
- {% for attribute in manager.get_handlers()[handler].get_storage_attributes() %}
-
- {{ attribute }}
-
- {% endfor %}
- {% endif %}
-
- {% endfor %}
-
Events
- {% for handler in manager.get_handlers() %}
-
- {% set event_names = manager.event_manager.get_storage_events_names(handler) %}
- {% if event_names["in"] or event_names["out"] %}
-
- {% for event in event_names["in"] %}
-
- ← {{ event }}
-
- {% endfor %}
- {% for event in event_names["out"] %}
-
- → {{ event }}
-
- {% endfor %}
- {% endif %}
-
- {% endfor %}
-
-
-
- Save view
-
-
- {{ button("Save", "window.app.inspector.saveView()", "save.png", ["small"]) }}
- {{ button("Delete", "window.app.inspector.deleteView()", "trash_white.png", ["small", "red"]) }}
-
-
-
-
diff --git a/modules/web_server/templates/pages/overview.html b/modules/web_server/templates/pages/overview.html
deleted file mode 100644
index 7c77d3d..0000000
--- a/modules/web_server/templates/pages/overview.html
+++ /dev/null
@@ -1,68 +0,0 @@
-Overview
-
-
-
- {% if messages %}
-
- {% else %}
- Nothing to display
- {% endif %}
- {% for message in messages -%}
-
-
- {% if message.incoming %}
- Incoming
- {% else %}
- Outgoing
- {% if message.type == "event" %}
-
- {% endif %}
- {% endif %}
-
-
- {{ message.type }}
-
-
- {{ message.time }}
-
-
-
- {% if message.handler %}
-
- {{ message.handler.get_name() }}
- {% else %}
- Deleted handler
- {% endif %}
-
- {{ message.handler_id }}
-
-
-
- {%- if message.type == "event" -%}
- {{ message.data.label }}:
- {% if "routine_log" in message %}
- {{ message.routine_log[-1][1] }}
- {% else %}
- {{ message.data.payload }}
- {% endif %}
- {%- else -%}
- {{ message.data }}
- {%- endif -%}
-
-
-
- {% endfor %}
-
-
-
diff --git a/modules/web_server/templates/partials/navbar.html b/modules/web_server/templates/partials/navbar.html
deleted file mode 100644
index e69de29..0000000
diff --git a/modules/web_server/templates/partials/notifications.html b/modules/web_server/templates/partials/notifications.html
deleted file mode 100644
index ddc89f5..0000000
--- a/modules/web_server/templates/partials/notifications.html
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
diff --git a/package.json b/package.json
deleted file mode 100644
index 198dced..0000000
--- a/package.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "name": "contwatch",
- "version": "1.1.0",
- "type": "module",
- "description": "Scalable system for IoT automatization",
- "repository": {
- "type": "git",
- "url": "git+https://github.com/BlueManCZ/contwatch.git"
- },
- "author": "Ivo Šmerek",
- "bugs": {
- "url": "https://github.com/BlueManCZ/contwatch/issues"
- },
- "scripts": {
- "start": "npm run build; ./run.py",
- "build": "node --loader ts-node/esm $(npm bin)/webpack",
- "sass-lint": "$(npm bin)/sass-lint -c sass-lint.yml 'modules/web_server/templates/components/**/*.sass' -v",
- "sass-lint:fix": "node_modules/sass-lint-auto-fix/dist/index.js --config-sass-lint sass-lint.yml"
- },
- "homepage": "https://github.com/BlueManCZ/contwatch#readme",
- "devDependencies": {
- "@babel/preset-env": "^7.18.10",
- "@babel/preset-typescript": "^7.18.6",
- "@typescript-eslint/eslint-plugin": "^5.6.0",
- "@typescript-eslint/parser": "^5.6.0",
- "babel": "^6.23.0",
- "babel-loader": "^8.2.5",
- "eslint": "^7.32.0",
- "eslint-config-standard": "^16.0.3",
- "eslint-plugin-import": "^2.25.3",
- "eslint-plugin-node": "^11.1.0",
- "eslint-plugin-promise": "^5.2.0",
- "sass-lint": "^1.13.1",
- "sass-lint-auto-fix": "^0.21.2",
- "ts-loader": "^9.2.6",
- "ts-node": "^10.9.1",
- "typescript": "^4.5.3",
- "webpack": "^5.74.0",
- "webpack-cli": "^4.10.0"
- }
-}
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 5f34921..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,8 +0,0 @@
-eventlet
-flask
-flask-socketio
-minimalmodbus
-pony
-pyserial
-requests
-simplejson
diff --git a/run.py b/run.py
deleted file mode 100755
index 800ab95..0000000
--- a/run.py
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/usr/bin/env python3
-
-from eventlet import monkey_patch
-
-monkey_patch()
-
-from modules import settings
-from modules.logging.logger import logger
-from modules.core import HandlerManager, database
-from modules.web_server.flask_web_server import FlaskWebServer
-from modules.tools.modules_registrator import ModulesRegistrator
-
-from json import dumps, load
-from optparse import OptionParser
-from signal import signal, SIGINT
-
-
-registered_modules = ModulesRegistrator()
-
-
-def _quit_handler(_, __):
- """Handler for exit signal."""
- print("\nSIGINT signal detected. Exiting")
- _quit()
-
-
-def _quit():
- registered_modules.exit()
- quit()
-
-
-if __name__ == "__main__":
- signal(SIGINT, _quit_handler)
-
- parser = OptionParser()
-
- parser.add_option(
- "-e",
- "--export-config",
- action="store_true",
- dest="export_config",
- default=False,
- help="export configuration in JSON format",
- )
-
- parser.add_option(
- "-i",
- "--import-config",
- dest="import_config",
- help="import configuration from JSON_FILE",
- metavar="JSON_FILE",
- )
-
- (options, args) = parser.parse_args()
-
- log = logger("Main")
- log.info("Starting application")
-
- # Initialize main modules
- db = database.Database()
- manager = HandlerManager(db)
-
- # Register modules for SIGINT handler
- registered_modules.add(db, manager)
-
- if options.export_config:
- data = manager.export_config()
- print(dumps(data, indent=4, ensure_ascii=False))
- _quit()
-
- if options.import_config:
- manager.delete_all()
- database.delete_tables()
- database.create_tables()
- file = open(options.import_config, "r")
- manager.import_config(load(file))
- _quit()
-
- settings.print_info()
-
- if settings.WEB_SERVER:
- web = FlaskWebServer(manager, db)
- registered_modules.add(web)
-
- # Wait for the manager thread to end
- manager.thread.join()
diff --git a/sass-lint.yml b/sass-lint.yml
deleted file mode 100644
index d27141d..0000000
--- a/sass-lint.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-rules:
- property-sort-order:
- - 1
- - order: 'smacss'
- class-name-format:
- - 1
- - convention: 'hyphenatedbem'
- quotes:
- - 1
- - style: 'single'
- placeholder-in-extend:
- - 0
- nesting-depth:
- - 1
- - max-depth: 3
diff --git a/settings.py b/settings.py
deleted file mode 100644
index b5c9cfa..0000000
--- a/settings.py
+++ /dev/null
@@ -1,101 +0,0 @@
-# ContWatch configuration settings file
-
-# File lookup takes place in the following order:
-# 1) ~/.config/contwatch/settings.py
-# 2) project-root/settings.py
-
-
-# ----| LOGGING |----
-
-# Absolute or relative path to log file
-# Relative path is relative to the project root.
-# Default: "contwatch.log"
-LOG_FILE = "contwatch.log"
-
-# Maximum log file size in bytes
-# Default: 51200
-LOG_FILE_MAX_BYTES = 51200
-
-# Number of maximum backup log files
-# Default: 5
-LOG_FILE_BACKUP_COUNT = 5
-
-# Logging level
-# For more information see: https://docs.python.org/3/library/logging.html#logging-levels
-# Default: "DEBUG"
-LOG_LEVEL = "DEBUG"
-
-
-# ----| DATABASE |----
-
-# Database type
-# Currently available types: sqlite, mysql
-# Default: "sqlite"
-DB_TYPE = "sqlite"
-
-# Absolute or relative path to database file
-# Relative path is relative to the project root.
-# Default: "database.sqlite"
-DB_SQLITE_FILE = "database.sqlite"
-
-# Database host address
-# An empty string means localhost address.
-# Default: ""
-DB_HOST = ""
-
-# Database user
-# An empty string means the current user will be used.
-# Default: ""
-DB_USER = ""
-
-# Database password
-# Default: ""
-DB_PASSWORD = ""
-
-# Database name
-# Default: "contwatch"
-DB_DATABASE = "contwatch"
-
-# Do not save incoming data to the database
-# Default: False
-DB_DATA_READONLY = False
-
-
-# ----| WEB SERVER |----
-
-# Enable or disable web server module.
-# Default: True
-WEB_SERVER = True
-
-# Web server address
-# Default: "0.0.0.0"
-WEB_SERVER_ADDRESS = "0.0.0.0"
-
-# Web server port
-# Default: 80
-WEB_SERVER_PORT = 80
-
-# Enable debug mode for web server. Do not use it in a production deployment.
-# Default: False
-WEB_SERVER_DEBUG = False
-
-# Specify which origins are allowed to connect to Flask-SocketIO server.
-# This value is passed as it is to the `cors_allowed_origins` parameter of flask_socketio.SocketIO server.
-# For more information search for `cors_allowed_origins` on https://flask-socketio.readthedocs.io/en/latest/api.html
-# Default: "*"
-WEB_SERVER_ORIGINS = "*"
-
-
-# ----| CACHING | ----
-
-# Caching interval in minutes
-# Cache is stored in RAM.
-# 0 means caching is disabled.
-# Default: 10
-CACHING_INTERVAL = 10
-
-# Asynchronous caching
-# Cache data will be refreshed asynchronously every CACHING_INTERVAL minutes.
-# With async caching enabled you can get better response time on slower hardware.
-# Default: False
-CACHING_ASYNC = False
diff --git a/src/client/.gitignore b/src/client/.gitignore
new file mode 100644
index 0000000..96fab4f
--- /dev/null
+++ b/src/client/.gitignore
@@ -0,0 +1,38 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# Dependencies
+node_modules
+.pnp
+.pnp.js
+
+# Local env files
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# Testing
+coverage
+
+# Turbo
+.turbo
+
+# Vercel
+.vercel
+
+# Build Outputs
+.next/
+out/
+build
+dist
+
+
+# Debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Misc
+.DS_Store
+*.pem
diff --git a/src/client/.prettierrc b/src/client/.prettierrc
new file mode 100644
index 0000000..22df8e5
--- /dev/null
+++ b/src/client/.prettierrc
@@ -0,0 +1,9 @@
+printWidth: 110
+tabWidth: 4
+singleQuote: false
+semi: true
+useTabs: false
+trailingComma: "all"
+proseWrap: "always"
+bracketSpacing: true
+jsxBracketSameLine: false
diff --git a/src/client/.stylelintrc.json b/src/client/.stylelintrc.json
new file mode 100644
index 0000000..878410f
--- /dev/null
+++ b/src/client/.stylelintrc.json
@@ -0,0 +1,10 @@
+{
+ "extends": [
+ "stylelint-config-standard-scss",
+ "stylelint-config-prettier-scss",
+ "stylelint-config-property-sort-order-smacss"
+ ],
+ "plugins": [
+ "stylelint-order"
+ ]
+}
diff --git a/src/client/README.md b/src/client/README.md
new file mode 100644
index 0000000..1574369
--- /dev/null
+++ b/src/client/README.md
@@ -0,0 +1,84 @@
+# Turborepo starter
+
+This is an official starter Turborepo.
+
+## Using this example
+
+Run the following command:
+
+```sh
+npx create-turbo@latest
+```
+
+## What's inside?
+
+This Turborepo includes the following packages/apps:
+
+### Apps and Packages
+
+- `docs`: a [Next.js](https://nextjs.org/) app
+- `web`: another [Next.js](https://nextjs.org/) app
+- `@repo/ui`: a stub React component library shared by both `web` and `docs` applications
+- `@repo/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
+- `@repo/typescript-config`: `tsconfig.json`s used throughout the monorepo
+
+Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
+
+### Utilities
+
+This Turborepo has some additional tools already setup for you:
+
+- [TypeScript](https://www.typescriptlang.org/) for static type checking
+- [ESLint](https://eslint.org/) for code linting
+- [Prettier](https://prettier.io) for code formatting
+
+### Build
+
+To build all apps and packages, run the following command:
+
+```
+cd my-turborepo
+pnpm build
+```
+
+### Develop
+
+To develop all apps and packages, run the following command:
+
+```
+cd my-turborepo
+pnpm dev
+```
+
+### Remote Caching
+
+> [!TIP]
+> Vercel Remote Cache is free for all plans. Get started today at [vercel.com](https://vercel.com/signup?/signup?utm_source=remote-cache-sdk&utm_campaign=free_remote_cache).
+
+Turborepo can use a technique known as [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines.
+
+By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup?utm_source=turborepo-examples), then enter the following commands:
+
+```
+cd my-turborepo
+npx turbo login
+```
+
+This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview).
+
+Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo:
+
+```
+npx turbo link
+```
+
+## Useful Links
+
+Learn more about the power of Turborepo:
+
+- [Tasks](https://turbo.build/repo/docs/core-concepts/monorepos/running-tasks)
+- [Caching](https://turbo.build/repo/docs/core-concepts/caching)
+- [Remote Caching](https://turbo.build/repo/docs/core-concepts/remote-caching)
+- [Filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
+- [Configuration Options](https://turbo.build/repo/docs/reference/configuration)
+- [CLI Usage](https://turbo.build/repo/docs/reference/command-line-reference)
diff --git a/src/client/apps/contwatch-client/.gitignore b/src/client/apps/contwatch-client/.gitignore
new file mode 100644
index 0000000..f886745
--- /dev/null
+++ b/src/client/apps/contwatch-client/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# env files (can opt-in for commiting if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/src/client/apps/contwatch-client/Dockerfile b/src/client/apps/contwatch-client/Dockerfile
new file mode 100644
index 0000000..3cb1d80
--- /dev/null
+++ b/src/client/apps/contwatch-client/Dockerfile
@@ -0,0 +1,44 @@
+FROM node:22-alpine AS base
+ENV PNPM_HOME="/pnpm"
+ENV PATH="$PNPM_HOME:$PATH"
+RUN corepack enable
+RUN apk update
+# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
+RUN apk add --no-cache libc6-compat
+WORKDIR /contwatch-client
+
+FROM base AS prepare
+RUN pnpm install turbo
+COPY . .
+RUN pnpm turbo prune contwatch-client --docker
+
+# Add lockfile and package.json's of isolated subworkspace
+FROM base AS builder
+# First install dependencies (as they change less often)
+COPY --from=prepare /contwatch-client/out/json/ .
+RUN pnpm install
+
+# Build the project and its dependencies
+COPY --from=prepare /contwatch-client/out/full/ .
+
+# Uncomment and use build args to enable remote caching
+ARG TURBO_TEAM
+ENV TURBO_TEAM=$TURBO_TEAM
+
+ARG TURBO_TOKEN
+ENV TURBO_TOKEN=$TURBO_TOKEN
+
+RUN pnpm turbo build --filter=contwatch-client...
+
+FROM base AS runner
+# Don't run production as root
+RUN addgroup --system --gid 1001 nodejs
+RUN adduser --system --uid 1001 nextjs
+USER nextjs
+ENV HOSTNAME=0.0.0.0
+
+COPY --from=builder --chown=nextjs:nodejs /contwatch-client/apps/contwatch-client/.next/standalone ./
+COPY --from=builder --chown=nextjs:nodejs /contwatch-client/apps/contwatch-client/.next/static ./apps/contwatch-client/.next/static
+COPY --from=builder --chown=nextjs:nodejs /contwatch-client/apps/contwatch-client/public ./apps/contwatch-client/public
+
+CMD node apps/contwatch-client/server.js
diff --git a/src/client/apps/contwatch-client/README.md b/src/client/apps/contwatch-client/README.md
new file mode 100644
index 0000000..a98bfa8
--- /dev/null
+++ b/src/client/apps/contwatch-client/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/src/client/apps/contwatch-client/app/[lang]/APIModelsDefinitions.ts b/src/client/apps/contwatch-client/app/[lang]/APIModelsDefinitions.ts
new file mode 100644
index 0000000..4b6396c
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/APIModelsDefinitions.ts
@@ -0,0 +1,23 @@
+import { Endpoint } from "@repo/utils/endpoints";
+import { getApiEndpoint } from "@repo/utils/getApiEndpoint";
+import { io, type Socket } from "socket.io-client";
+import { customMutate, SWRModelEndpoint } from "swr-models";
+
+import { fetchJson } from "../../src/utils";
+
+export const Handlers = new SWRModelEndpoint({
+ key: getApiEndpoint(Endpoint.handlers),
+ serverFetcher: fetchJson,
+});
+export const Attributes = new SWRModelEndpoint({
+ key: getApiEndpoint(Endpoint.attributes),
+ serverFetcher: fetchJson,
+});
+export const DataStats = new SWRModelEndpoint({
+ key: getApiEndpoint(Endpoint.dataStats),
+ serverFetcher: fetchJson,
+});
+
+export const socket: Socket = io();
+
+socket.on("mutate", (endpoint: string) => customMutate(getApiEndpoint(endpoint)));
diff --git a/src/client/apps/contwatch-client/app/[lang]/actions/page.tsx b/src/client/apps/contwatch-client/app/[lang]/actions/page.tsx
new file mode 100644
index 0000000..d3f875a
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/actions/page.tsx
@@ -0,0 +1,102 @@
+"use client";
+
+// import "@xyflow/react/dist/base.css";
+import "@xyflow/react/dist/style.css";
+
+import { Text } from "@repo/ui/Text";
+import { useTranslation } from "@repo/utils/useTranslation";
+import {
+ addEdge,
+ Background,
+ BackgroundVariant,
+ Controls,
+ type Edge,
+ MiniMap,
+ type Node,
+ Position,
+ ReactFlow,
+ useEdgesState,
+ useNodesState,
+} from "@xyflow/react";
+import type { Connection } from "@xyflow/system";
+import { useCallback, useEffect } from "react";
+
+export default function Actions() {
+ // TODO: Use SSR translation and remove "use client"
+ const { t } = useTranslation();
+
+ const firstTitle = t("New action editor");
+ const secondTitle = t("is currently in development");
+
+ const initialNodes: Node[] = [
+ {
+ id: "1",
+ type: "input",
+ position: { x: 0, y: 0 },
+ data: { label: firstTitle },
+ sourcePosition: Position.Right,
+ targetPosition: Position.Left,
+ },
+ {
+ id: "2",
+ type: "output",
+ position: { x: 200, y: 50 },
+ data: { label: secondTitle },
+ sourcePosition: Position.Right,
+ targetPosition: Position.Left,
+ },
+ ];
+
+ const initialEdges: Edge[] = [
+ {
+ id: "e1-2",
+ source: "1",
+ target: "2",
+ },
+ ];
+
+ const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
+ const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
+
+ useEffect(() => {
+ setNodes((nodes) =>
+ nodes.map((node) => {
+ if (node.id === "1") {
+ return { ...node, data: { ...node.data, label: firstTitle } };
+ }
+ if (node.id === "2") {
+ return { ...node, data: { ...node.data, label: secondTitle } };
+ }
+ return node;
+ }),
+ );
+ }, [firstTitle, secondTitle, setNodes]);
+
+ const onConnect = useCallback(
+ (params: Connection) => setEdges((eds) => addEdge(params, eds)),
+ [setEdges],
+ );
+
+ return (
+ <>
+
+ {t("Actions")}
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/globals.scss b/src/client/apps/contwatch-client/app/[lang]/globals.scss
new file mode 100644
index 0000000..958f9ad
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/globals.scss
@@ -0,0 +1 @@
+@use "../../../../packages/ui/src/components/common";
diff --git a/src/client/apps/contwatch-client/app/[lang]/handlers/[handlerId]/page.tsx b/src/client/apps/contwatch-client/app/[lang]/handlers/[handlerId]/page.tsx
new file mode 100644
index 0000000..68f4734
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/handlers/[handlerId]/page.tsx
@@ -0,0 +1,59 @@
+import fs from "node:fs";
+
+import type { AttributeModel } from "@repo/types/AttributeModel";
+import type { HandlerModel } from "@repo/types/HandlerModel";
+import type { IconType } from "@repo/types/IconType";
+import type { PageParams } from "@repo/types/PageProps";
+import { Text } from "@repo/ui/Text";
+import { ssrTranslation } from "@repo/utils/ssrTranslation";
+import Link from "next/link";
+import { CustomSWRConfig } from "swr-models";
+
+import { Attributes, Handlers } from "../../APIModelsDefinitions";
+import { HandlersWrapper } from "../components/HandlersWrapper/HandlersWrapper";
+import { HandlerWidget } from "../components/HandlerWidget/HandlerWidget";
+
+type HandlersPageParams = PageParams & {
+ params: Promise<{
+ handlerId: string;
+ }>;
+};
+
+export default async function HandlersPage({ params }: HandlersPageParams) {
+ const lang = (await params).lang;
+ const { t } = await ssrTranslation(lang);
+
+ const handlerId = Number.parseInt((await params).handlerId);
+
+ // Fetch one handler object for fallback
+ const fallbackHandler = await Handlers.fetch({ id: handlerId });
+
+ // Fetch all attribute objects for fallback
+ const attributesFallback: Record = {};
+ for (const attribute of await Attributes.fetch()) {
+ attributesFallback[Attributes.endpoint({ id: attribute.id })] = attribute;
+ }
+
+ const attributeIcons: IconType[] = [];
+ for (const file of fs.readdirSync("public/icons").filter((file) => file.endsWith(".svg"))) {
+ attributeIcons.push(file.replace(".svg", "") as IconType);
+ }
+
+ return (
+
+
+ {t("Handlers")} · {fallbackHandler.name}
+
+
+
+
+
+ );
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/handlers/components/AttributeWidget/AttributeWidget.module.scss b/src/client/apps/contwatch-client/app/[lang]/handlers/components/AttributeWidget/AttributeWidget.module.scss
new file mode 100644
index 0000000..9d2a92d
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/handlers/components/AttributeWidget/AttributeWidget.module.scss
@@ -0,0 +1,55 @@
+.attribute-widget {
+ display: flex;
+ align-items: center;
+ margin: 0 .5rem;
+ padding: .4rem;
+ border: dashed 2px var(--color-mono-white);
+ border-radius: var(--border-radius-small);
+ color: inherit;
+ text-decoration: none;
+ gap: .5rem;
+ cursor: grab;
+
+ &--dragging {
+ position: relative;
+ z-index: 10;
+ border-color: var(--color-mono-dark) !important;
+ background: var(--color-mono-light) !important;
+ }
+
+ &--unloaded {
+ opacity: .5;
+ }
+
+ &:hover {
+ border-color: var(--color-mono-light);
+ background: var(--color-mono-light);
+ }
+
+ &:active {
+ cursor: move;
+ }
+
+ &__peak-indicator {
+ display: inline-flex;
+ justify-content: space-between;
+ margin-left: 5px;
+ padding: 0.2rem;
+ border-radius: 100%;
+ opacity: .9;
+
+ &--color-green {
+ background: var(--color-green);
+ }
+
+ &--color-red {
+ background: var(--color-red);
+ }
+ }
+
+ &__value {
+ padding: 5px 10px;
+ border-radius: 10px;
+ background: var(--color-mono-light);
+ }
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/handlers/components/AttributeWidget/AttributeWidget.tsx b/src/client/apps/contwatch-client/app/[lang]/handlers/components/AttributeWidget/AttributeWidget.tsx
new file mode 100644
index 0000000..d55e11f
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/handlers/components/AttributeWidget/AttributeWidget.tsx
@@ -0,0 +1,334 @@
+"use client";
+
+import { useSortable } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import type { AttributeModel } from "@repo/types/AttributeModel";
+import type { DataStatModel } from "@repo/types/DataStatModel";
+import type { IconType } from "@repo/types/IconType";
+import { Button } from "@repo/ui/Button";
+import { Flex } from "@repo/ui/Flex";
+import { Column } from "@repo/ui/FlexPartials";
+import { Icon } from "@repo/ui/Icon";
+import { Input } from "@repo/ui/Input";
+import { Popup } from "@repo/ui/Popup";
+import { Separator } from "@repo/ui/Separator";
+import { Text } from "@repo/ui/Text";
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import { useTranslation } from "@repo/utils/useTranslation";
+import { useRouter } from "next/navigation";
+import { type FC, useState } from "react";
+import { useModel } from "swr-models";
+
+import { Attributes, DataStats } from "../../../APIModelsDefinitions";
+import styles from "./AttributeWidget.module.scss";
+
+type AttributeWidgetProps = {
+ bareAttribute: AttributeModel;
+ draggable?: boolean;
+ editMode?: boolean;
+ attributeIcons?: IconType[];
+
+ onAttributeDelete(attributeId: number): void;
+};
+
+const bem = bemClassNames(styles);
+
+export const AttributeWidget: FC = ({
+ bareAttribute,
+ draggable,
+ editMode,
+ onAttributeDelete,
+ attributeIcons,
+}) => {
+ const { t } = useTranslation();
+ const router = useRouter();
+
+ const [editPopupOpen, setEditPopupOpen] = useState(false);
+ const [iconSelectOpen, setIconSelectOpen] = useState(false);
+
+ const {
+ model: attribute = bareAttribute,
+ set: setAttribute,
+ reset: resetAttribute,
+ original: originalAttribute,
+ lock: lockAttribute,
+ unlock: unlockAttribute,
+ commit: commitAttribute,
+ } = useModel(Attributes, { id: bareAttribute.id });
+
+ const { model: dataStats } = useModel(DataStats, {
+ params: {
+ attribute: bareAttribute.id.toString(),
+ },
+ });
+
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
+ id: bareAttribute.id,
+ disabled: !draggable,
+ });
+
+ const style = {
+ transform: CSS.Transform.toString(transform),
+ transition,
+ };
+
+ const isLoaded = attribute?.data?.trend !== undefined;
+ const getAttributeName = () => {
+ if (attribute.label && attribute.label !== "") {
+ return attribute.label;
+ }
+ return attribute.name;
+ };
+
+ const minStat = dataStats?.find((stat) => stat.type === "min");
+ const maxStat = dataStats?.find((stat) => stat.type === "max");
+
+ const onPopupSave = () => {
+ commitAttribute();
+ setEditPopupOpen(false);
+ };
+
+ return (
+ <>
+ {/** biome-ignore lint/a11y/noStaticElementInteractions: off */}
+ {/** biome-ignore lint/a11y/useKeyWithClickEvents: off */}
+ {
+ if (editMode) {
+ if (isLoaded) {
+ setEditPopupOpen(true);
+ }
+ } else {
+ router.push(`/inspector?attribute=${bareAttribute.id}`);
+ }
+ }}
+ >
+
+
+
+
+ {getAttributeName()}
+ {editMode ? (
+ attribute.base_unit && ` (${attribute.base_unit})`
+ ) : (
+ <>
+ {minStat?.value === attribute.data?.value &&
+ minStat?.value !== maxStat?.value && (
+
+
+
+ )}
+ {maxStat?.value === attribute.data?.value &&
+ minStat?.value !== maxStat?.value && (
+
+
+
+ )}
+ {attribute.data?.trend === -1 && (
+
+
+
+ )}
+ {attribute.data?.trend === 1 && (
+
+
+
+ )}
+ >
+ )}
+
+
+ {editMode ? (
+ {attribute.name}
+ ) : (
+ <>
+ {minStat && (
+
+
+
+ {minStat.value} {minStat.unit}
+
+
+ )}
+ {maxStat && (
+
+
+
+ {maxStat.value} {maxStat.unit}
+
+
+ )}
+ >
+ )}
+
+
+
+
+
+ {editMode ? (
+
+ ) : (
+
+ {attribute.data?.value} {attribute.data?.unit}
+
+ )}
+
+ {editMode && editPopupOpen && (
+ {
+ lockAttribute();
+ }}
+ onClose={() => {
+ resetAttribute();
+ unlockAttribute();
+ setEditPopupOpen(false);
+ }}
+ onEnter={onPopupSave}
+ title={originalAttribute?.label ?? originalAttribute?.name}
+ >
+
+
+ setAttribute((a) => {
+ if (!a) return a;
+ return {
+ ...a,
+ label: value,
+ };
+ })
+ }
+ focus
+ />
+
+ setAttribute((a) => {
+ if (!a) return a;
+ return {
+ ...a,
+ base_unit: value,
+ };
+ })
+ }
+ />
+
+ setAttribute((a) => {
+ if (!a) return a;
+ return {
+ ...a,
+ rounding: value,
+ };
+ })
+ }
+ />
+
+ setAttribute((a) => {
+ if (!a) return a;
+ return {
+ ...a,
+ color: value,
+ };
+ })
+ }
+ />
+
+ {t("Icon")}
+
+
+ setAttribute((a) => {
+ if (!a) return a;
+ return {
+ ...a,
+ icon: value as IconType,
+ };
+ })
+ }
+ />
+ setIconSelectOpen(true)}
+ />
+ setIconSelectOpen(false)}
+ title={t("Select icon")}
+ >
+
+ {attributeIcons?.map((icon) => (
+ {
+ setAttribute((a) => {
+ if (!a) return a;
+ return {
+ ...a,
+ icon,
+ };
+ });
+ setIconSelectOpen(false);
+ }}
+ />
+ ))}
+
+
+
+
+
+
+ {
+ // Are you sure check
+ if (
+ confirm(
+ `${t("Do you want to delete attribute")} ${getAttributeName()}?`,
+ )
+ ) {
+ onAttributeDelete(bareAttribute.id);
+ }
+ }}
+ variant={"red"}
+ />
+
+
+ {t("Save")}
+
+
+
+
+ )}
+ >
+ );
+};
diff --git a/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlerWidget/HandlerWidget.module.scss b/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlerWidget/HandlerWidget.module.scss
new file mode 100644
index 0000000..43efc88
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlerWidget/HandlerWidget.module.scss
@@ -0,0 +1,38 @@
+.handler-widget {
+ width: 500px;
+ overflow: hidden;
+ border-radius: 20px;
+ background: white;
+ box-shadow: var(--box-shadow-card);
+
+ @media (width <= 1000px) {
+ width: 100%;
+ }
+
+ &__header {
+ display: flex;
+ align-items: center;
+ padding: 0.6rem 1rem;
+ background-color: var(--color-primary);
+ color: var(--color-mono-light);
+ gap: .5rem;
+ text-decoration: none;
+
+ &--color-0 {
+ background: #682723;
+ }
+
+ &--color-1 {
+ background: #125100;
+ }
+
+ &--color-2 {
+ background: #9b6d00;
+ }
+ }
+
+ &__body {
+ padding: .5rem 0;
+ overflow: auto;
+ }
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlerWidget/HandlerWidget.tsx b/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlerWidget/HandlerWidget.tsx
new file mode 100644
index 0000000..b6c63ec
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlerWidget/HandlerWidget.tsx
@@ -0,0 +1,275 @@
+"use client";
+
+import {
+ closestCenter,
+ DndContext,
+ type DragEndEvent,
+ KeyboardSensor,
+ MouseSensor,
+ TouchSensor,
+ useSensor,
+ useSensors,
+} from "@dnd-kit/core";
+import { restrictToParentElement, restrictToVerticalAxis } from "@dnd-kit/modifiers";
+import {
+ arrayMove,
+ SortableContext,
+ sortableKeyboardCoordinates,
+ verticalListSortingStrategy,
+} from "@dnd-kit/sortable";
+import type { HandlerModel } from "@repo/types/HandlerModel";
+import type { IconType } from "@repo/types/IconType";
+import { Button } from "@repo/ui/Button";
+import { Flex } from "@repo/ui/Flex";
+import { Column } from "@repo/ui/FlexPartials";
+import { Icon } from "@repo/ui/Icon";
+import { Text } from "@repo/ui/Text";
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import { jsonFetcher } from "@repo/utils/communication";
+import { useTranslation } from "@repo/utils/useTranslation";
+import { DateTime } from "luxon";
+import Link from "next/link";
+import type { FC } from "react";
+import { useModel } from "swr-models";
+
+import { Handlers } from "../../../APIModelsDefinitions";
+import { AttributeWidget } from "../AttributeWidget/AttributeWidget";
+import styles from "./HandlerWidget.module.scss";
+
+type HandlerWidgetProps = {
+ handlerId: number;
+ editMode?: boolean;
+ attributeIcons?: IconType[];
+};
+
+const bem = bemClassNames(styles);
+
+export const HandlerWidget: FC = ({ handlerId, editMode, attributeIcons }) => {
+ const { t } = useTranslation();
+
+ const {
+ model: handler,
+ set: setHandlerState,
+ commit: commitHandler,
+ } = useModel(Handlers, { id: handlerId });
+ const attributeIds = handler?.attributes.map((a) => a.id) ?? [];
+
+ const sensors = useSensors(
+ useSensor(MouseSensor, {
+ activationConstraint: {
+ distance: 8,
+ },
+ }),
+ useSensor(TouchSensor, {
+ activationConstraint: {
+ delay: 300,
+ tolerance: 8,
+ },
+ }),
+ useSensor(KeyboardSensor, {
+ coordinateGetter: sortableKeyboardCoordinates,
+ }),
+ );
+
+ const handleDragEnd = (event: DragEndEvent) => {
+ const { active, over } = event;
+
+ if (active.id !== over?.id) {
+ setHandlerState((handler) => {
+ if (!handler) return handler;
+
+ const oldIndex = handler.attributes.findIndex((attribute) => attribute.id === active.id);
+ const newIndex = handler.attributes.findIndex((attribute) => attribute.id === over?.id);
+
+ return {
+ ...handler,
+ attributes: arrayMove(handler.attributes, oldIndex, newIndex),
+ };
+ });
+ commitHandler();
+ }
+ };
+
+ return (
+ handler && (
+
+
+
+
+
+ {handler.name}
+
+
+ {handler.description}
+
+
+
+
+ {t("Data received")}:{" "}
+
+ {handler.last_message !== null
+ ? (handler.last_message ?? 0) < 10
+ ? t("Now")
+ : DateTime.local()
+ .minus({ seconds: handler.last_message })
+ .toRelative()
+ : t("Never")}
+
+
+
+ {t("Handler ID")}: {handler.id}
+
+
+
+ {(attributeIds.length > 0 || (editMode && handler.availableAttributes.length > 0)) && (
+ {
+ navigator.vibrate?.(40);
+ }}
+ >
+
+
+ {editMode && Object.keys(handler.actions).length > 0 && (
+ <>
+
+
+ {t("Available actions")}
+
+
+
+
+ {Object.keys(handler.actions).map((action) => {
+ return (
+ {
+ navigator.vibrate?.(40);
+ jsonFetcher(
+ Handlers.endpoint({
+ id: `${handler.id}/action/${action}`,
+ }),
+ "PUT",
+ );
+ }}
+ >
+ {handler.actions[action]?.description}
+
+ );
+ })}
+
+
+ >
+ )}
+ {editMode && attributeIds.length > 0 && (
+
+
+ {t("Stored attributes")}
+
+
+ )}
+ {handler.attributes.map((bareAttribute) => {
+ return (
+
{
+ setHandlerState((handler) => {
+ if (!handler) return handler;
+
+ const removedAttribute = handler.attributes.find(
+ (attribute) => attribute.id === bareAttribute.id,
+ );
+ const newAttributes = handler.attributes.filter(
+ (attribute) => attribute.id !== bareAttribute.id,
+ );
+ return {
+ ...handler,
+ attributes: newAttributes,
+ availableAttributes: [
+ {
+ name: removedAttribute?.name ?? "",
+ value: "",
+ },
+ ...handler.availableAttributes,
+ ],
+ };
+ });
+ commitHandler();
+ }}
+ {...{ bareAttribute, editMode, attributeIcons }}
+ />
+ );
+ })}
+ {editMode && handler.availableAttributes.length > 0 && (
+
+
+ {t("Available attributes")}
+
+
+ )}
+
+ {editMode &&
+ handler.availableAttributes?.map((attribute) => {
+ return (
+
+
+
+
+ {attribute.name}
+
+
+ {t("Latest value")}:{" "}
+ {attribute.value.toString()}
+
+
+ {
+ setHandlerState((handler) => {
+ if (!handler) return handler;
+
+ const newAttributes = [
+ ...handler.attributes,
+ {
+ id: -1,
+ name: attribute.name,
+ },
+ ];
+ return {
+ ...handler,
+ attributes: newAttributes,
+ availableAttributes:
+ handler.availableAttributes.filter(
+ (a) => a.name !== attribute.name,
+ ),
+ };
+ });
+ commitHandler();
+ }}
+ />
+
+ );
+ })}
+
+
+
+
+ )}
+
+ )
+ );
+};
diff --git a/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlersWrapper/HandlersWrapper.module.scss b/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlersWrapper/HandlersWrapper.module.scss
new file mode 100644
index 0000000..5a4900c
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlersWrapper/HandlersWrapper.module.scss
@@ -0,0 +1,14 @@
+.handlers-wrapper {
+ content: "";
+ flex-grow: 1;
+ flex-wrap: wrap;
+ align-content: start;
+ height: 0;
+ gap: 1rem;
+
+ @media (width <= 1000px) {
+ flex-wrap: nowrap !important;
+ width: 100%;
+ height: unset;
+ }
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlersWrapper/HandlersWrapper.tsx b/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlersWrapper/HandlersWrapper.tsx
new file mode 100644
index 0000000..d60f8fe
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/handlers/components/HandlersWrapper/HandlersWrapper.tsx
@@ -0,0 +1,13 @@
+import { Column } from "@repo/ui/FlexPartials";
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import type { FC, PropsWithChildren } from "react";
+
+import styles from "./HandlersWrapper.module.scss";
+
+type HandlersWrapperProps = PropsWithChildren;
+
+const bem = bemClassNames(styles);
+
+export const HandlersWrapper: FC = ({ children }) => {
+ return {children} ;
+};
diff --git a/src/client/apps/contwatch-client/app/[lang]/handlers/components/IconSelector/IconSelector.tsx b/src/client/apps/contwatch-client/app/[lang]/handlers/components/IconSelector/IconSelector.tsx
new file mode 100644
index 0000000..9e02deb
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/handlers/components/IconSelector/IconSelector.tsx
@@ -0,0 +1,17 @@
+import * as fs from "node:fs";
+
+import { Icon, type IconProps } from "@repo/ui/Icon";
+
+export const IconSelector = () => {
+ const icons = fs.readdirSync("public/icons").map((file) => file.replace(".svg", "") as IconProps["icon"]);
+
+ return (
+
+ {icons.map((icon) => (
+
+
+
+ ))}
+
+ );
+};
diff --git a/src/client/apps/contwatch-client/app/[lang]/handlers/page.tsx b/src/client/apps/contwatch-client/app/[lang]/handlers/page.tsx
new file mode 100644
index 0000000..70b4d13
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/handlers/page.tsx
@@ -0,0 +1,52 @@
+import type { AttributeModel } from "@repo/types/AttributeModel";
+import type { DataStatModel } from "@repo/types/DataStatModel";
+import type { PageParams } from "@repo/types/PageProps";
+import { Text } from "@repo/ui/Text";
+import { ssrTranslation } from "@repo/utils/ssrTranslation";
+import { CustomSWRConfig } from "swr-models";
+
+import { Attributes, DataStats, Handlers } from "../APIModelsDefinitions";
+import { HandlersWrapper } from "./components/HandlersWrapper/HandlersWrapper";
+import { HandlerWidget } from "./components/HandlerWidget/HandlerWidget";
+
+export default async function PageHandlers({ params }: PageParams) {
+ const lang = (await params).lang;
+ const { t } = await ssrTranslation(lang);
+
+ // Fetch all handler ids
+ const handlerIds: number[] = await Handlers.fetch();
+
+ // Fetch all attribute objects for fallback
+ const attributesFallback: Record = {};
+ for (const attribute of await Attributes.fetch()) {
+ attributesFallback[Attributes.endpoint({ id: attribute.id })] = attribute;
+ }
+
+ // Fetch data stats for fallback
+ const dataStatsFallback: Record = {};
+ for (const attributeId of Object.values(attributesFallback).map((a) => a.id)) {
+ dataStatsFallback[DataStats.endpoint({ params: { attribute: attributeId.toString() } })] =
+ await DataStats.fetch({ params: { attribute: attributeId.toString() } });
+ }
+
+ return (
+
+
+ {t("Handlers")}
+
+
+ {handlerIds?.map((handlerId) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/inspector/components/AttributeCheckbox/AttributeCheckbox.tsx b/src/client/apps/contwatch-client/app/[lang]/inspector/components/AttributeCheckbox/AttributeCheckbox.tsx
new file mode 100644
index 0000000..ca56748
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/inspector/components/AttributeCheckbox/AttributeCheckbox.tsx
@@ -0,0 +1,31 @@
+import type { AttributeModel } from "@repo/types/AttributeModel";
+import { Input } from "@repo/ui/Input";
+import type { FC } from "react";
+
+import { Attributes } from "../../../APIModelsDefinitions";
+
+export type AttributeCheckboxProps = {
+ attributeId: number;
+ selectedAttributes: number[];
+ onAttributeClick: (attributeId: number) => void;
+};
+
+export const AttributeCheckbox: FC = ({
+ attributeId,
+ selectedAttributes,
+ onAttributeClick,
+}) => {
+ const { data: attribute } = Attributes.use({ id: attributeId });
+ return (
+ attribute && (
+ onAttributeClick(attribute.id)}
+ checked={selectedAttributes.includes(attribute.id)}
+ />
+ )
+ );
+};
diff --git a/src/client/apps/contwatch-client/app/[lang]/inspector/components/HandlerAttributes/HandlerAttributes.tsx b/src/client/apps/contwatch-client/app/[lang]/inspector/components/HandlerAttributes/HandlerAttributes.tsx
new file mode 100644
index 0000000..ffe01b3
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/inspector/components/HandlerAttributes/HandlerAttributes.tsx
@@ -0,0 +1,38 @@
+import type { AttributeModel } from "@repo/types/AttributeModel";
+import type { HandlerModel } from "@repo/types/HandlerModel";
+import { Column } from "@repo/ui/FlexPartials";
+import { Text } from "@repo/ui/Text";
+import type { FC } from "react";
+
+import { Handlers } from "../../../APIModelsDefinitions";
+import { AttributeCheckbox } from "../AttributeCheckbox/AttributeCheckbox";
+
+export type HandlerAttributesProps = {
+ handlerId: number;
+ attributes?: AttributeModel[];
+ selectedAttributes: number[];
+ onAttributeClick: (attributeId: number) => void;
+};
+
+export const HandlerAttributes: FC = ({
+ handlerId,
+ selectedAttributes,
+ onAttributeClick,
+}) => {
+ const { data: handler } = Handlers.use({ id: handlerId });
+ return (
+
+
+ {(handler?.attributes.length ?? 0) > 0 && handler?.name}
+
+
+ {handler?.attributes.map((attribute) => (
+
+ ))}
+
+
+ );
+};
diff --git a/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/InspectorChart.module.scss b/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/InspectorChart.module.scss
new file mode 100644
index 0000000..fe1d3d8
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/InspectorChart.module.scss
@@ -0,0 +1,17 @@
+.inspector-chart {
+ box-sizing: border-box;
+ flex-grow: 1;
+ padding: 1rem;
+ border-radius: 2rem;
+ background: white;
+
+ &--fullScreen {
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 100vh;
+ border-radius: unset;
+ }
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/InspectorChart.tsx b/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/InspectorChart.tsx
new file mode 100644
index 0000000..c65e3b2
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/InspectorChart.tsx
@@ -0,0 +1,205 @@
+import "chartjs-adapter-date-fns";
+
+import { selectLocaleState } from "@repo/store/slices/settingsSlice";
+import { Button } from "@repo/ui/Button";
+import { Flex } from "@repo/ui/Flex";
+import { Column } from "@repo/ui/FlexPartials";
+import { Input } from "@repo/ui/Input";
+import { Separator } from "@repo/ui/Separator";
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import { useTranslation } from "@repo/utils/useTranslation";
+import {
+ CategoryScale,
+ Chart,
+ type ChartOptions,
+ type ChartType,
+ Legend,
+ LinearScale,
+ LineElement,
+ PointElement,
+ TimeScale,
+ Title,
+ Tooltip,
+ type TooltipItem,
+} from "chart.js";
+import { type FC, useCallback, useEffect, useState } from "react";
+import { Line } from "react-chartjs-2";
+import { useSelector } from "react-redux";
+
+import { useAttributeChart } from "../../../swrUtils";
+import { options } from "./chartOptions";
+import styles from "./InspectorChart.module.scss";
+
+Chart.register(CategoryScale, LinearScale, TimeScale, PointElement, LineElement, Title, Tooltip, Legend);
+
+const bem = bemClassNames(styles);
+
+type InspectorChartProps = {
+ attributes?: number[];
+ date: string;
+ onSettingsClick?: () => void;
+ onDateChange?: (date: string) => void;
+};
+
+export const InspectorChart: FC = ({
+ attributes = [],
+ date,
+ onSettingsClick,
+ onDateChange,
+}) => {
+ const { t } = useTranslation();
+ const lng = useSelector(selectLocaleState);
+
+ // const ref = useRef(null);
+ const [ref, setRef] = useState(undefined);
+ const [zoomLevel, setZoomLevel] = useState(1);
+
+ const [fullScreen, setFullScreen] = useState(false);
+
+ // Loading the zoom plugin only on the client side because it doesn't support SSR
+ useEffect(() => {
+ if (typeof window !== "undefined")
+ import("chartjs-plugin-zoom").then((zoomPlugin) => {
+ Chart.register(zoomPlugin.default);
+ });
+ }, []);
+
+ const resetZoomLevel = useCallback(() => {
+ setZoomLevel(1);
+ ref?.resetZoom?.();
+ }, [ref]);
+
+ useEffect(() => {
+ resetZoomLevel();
+ }, [attributes, resetZoomLevel, date]);
+
+ // TODO: Fetch each attribute data separately, this is bad for caching.
+ const { data: attributeChartData } = useAttributeChart(attributes.sort(), date);
+
+ // UseEffect to exit fullscreen when the browser exits fullscreen
+ useEffect(() => {
+ const exitFullscreen = () => {
+ if (!document.fullscreenElement) {
+ setFullScreen(false);
+ }
+ };
+
+ document.addEventListener("fullscreenchange", exitFullscreen);
+
+ return () => {
+ document.removeEventListener("fullscreenchange", exitFullscreen);
+ };
+ }, []);
+
+ options.scales = {
+ ...options.scales,
+ x: {
+ ...options.scales?.x,
+ time: {
+ unit: "hour",
+ tooltipFormat: lng === "cs" ? "d.MM.yyyy HH:mm:ss" : "",
+ },
+ },
+ };
+
+ options.plugins.tooltip = {
+ callbacks: {
+ label: (chart: TooltipItem) => {
+ const value = chart.dataset.data[chart.dataIndex] as { x: number; y: number };
+ return `${chart.dataset.label}: ${value?.y} ${chart.dataset.stack ?? ""}`;
+ },
+ },
+ };
+
+ const data = {
+ datasets:
+ attributeChartData?.map((attributeChart) => ({
+ label: attributeChart.label,
+ stack: attributeChart.unit,
+ data: attributeChart.data.map((data) => ({
+ x: data.x * 1000,
+ y: data.y,
+ })),
+ borderColor: attributeChart.color ?? "#5278FF",
+ })) ?? [],
+ };
+
+ return (
+
+
+ {
+ onDateChange?.(
+ new Date(new Date(date).getTime() - 24 * 60 * 60 * 1000)
+ .toISOString()
+ .split("T")[0] ?? "",
+ );
+ }}
+ />
+
+ onDateChange?.(value !== "" ? value : (new Date().toISOString().split("T")[0] ?? ""))
+ }
+ />
+ {
+ onDateChange?.(
+ new Date(new Date(date).getTime() + 24 * 60 * 60 * 1000)
+ .toISOString()
+ .split("T")[0] ?? "",
+ );
+ }}
+ />
+ {fullScreen && }
+
+
+ , data }}
+ ref={(ref) => setRef(ref as unknown as Chart)}
+ onWheel={() => setZoomLevel(ref?.getZoomLevel?.() ?? 1)}
+ onTouchEnd={() => setZoomLevel(ref?.getZoomLevel?.() ?? 1)}
+ height={"100%"}
+ />
+
+
+
+ {t("Attributes")}
+
+ {
+ {
+ resetZoomLevel();
+ }}
+ />
+ }
+ {
+ setFullScreen((prev) => {
+ if (prev) {
+ document.exitFullscreen();
+ } else {
+ document.documentElement.requestFullscreen();
+ }
+ return !prev;
+ });
+ }}
+ >
+ {/*{t("Fullscreen")}*/}
+
+
+
+ );
+};
diff --git a/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/adapter.d.ts b/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/adapter.d.ts
new file mode 100644
index 0000000..1a8ad33
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/adapter.d.ts
@@ -0,0 +1 @@
+declare module "chartjs-adapter-date-fns";
diff --git a/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/chartOptions.ts b/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/chartOptions.ts
new file mode 100644
index 0000000..1f0dc61
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/inspector/components/InspectorChart/chartOptions.ts
@@ -0,0 +1,72 @@
+import type { Chart } from "chart.js";
+
+export const options = {
+ pointRadius: 0,
+ borderWidth: 1,
+ responsive: true,
+ animation: false,
+ maintainAspectRatio: false,
+ parsing: false,
+ interaction: {
+ mode: "nearest",
+ intersect: false,
+ axis: "xy",
+ },
+ scales: {
+ x: {
+ type: "time",
+ time: {
+ unit: "hour",
+ tooltipFormat: "",
+ },
+ beginAtZero: true,
+ },
+ y: {
+ beginAtZero: true,
+ stack: "main",
+ stacked: false,
+ },
+ },
+ plugins: {
+ zoom: {
+ zoom: {
+ wheel: {
+ enabled: true,
+ },
+ pinch: {
+ enabled: true,
+ },
+ mode: "x",
+ onZoomStart: ({ chart }: { chart: Chart }) => {
+ // @ts-expect-error Ignore
+ chart.config.options.plugins.tooltip.enabled = false;
+ },
+ onZoomComplete: ({ chart }: { chart: Chart }) => {
+ // @ts-expect-error Ignore
+ chart.config.options.plugins.tooltip.enabled = true;
+ chart.update();
+ },
+ },
+ pan: {
+ enabled: true,
+ mode: "x",
+ onPanStart: ({ chart }: { chart: Chart }) => {
+ // @ts-expect-error Ignore
+ chart.config.options.plugins.tooltip.enabled = false;
+ },
+ onPanComplete: ({ chart }: { chart: Chart }) => {
+ // @ts-expect-error Ignore
+ chart.config.options.plugins.tooltip.enabled = true;
+ chart.update();
+ },
+ },
+ limits: {
+ x: {
+ min: "original",
+ max: "original",
+ },
+ },
+ },
+ tooltip: {},
+ },
+};
diff --git a/src/client/apps/contwatch-client/app/[lang]/inspector/page.tsx b/src/client/apps/contwatch-client/app/[lang]/inspector/page.tsx
new file mode 100644
index 0000000..47b8b20
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/inspector/page.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import { Button } from "@repo/ui/Button";
+import { Column } from "@repo/ui/FlexPartials";
+import { Popup } from "@repo/ui/Popup";
+import { Text } from "@repo/ui/Text";
+import { useTranslation } from "@repo/utils/useTranslation";
+import { useSearchParams } from "next/navigation";
+import { useState } from "react";
+
+import { Handlers } from "../APIModelsDefinitions";
+import { HandlerAttributes } from "./components/HandlerAttributes/HandlerAttributes";
+import { InspectorChart } from "./components/InspectorChart/InspectorChart";
+
+export default function Inspector() {
+ const { t } = useTranslation();
+ const { data: handlers } = Handlers.use();
+
+ const searchParams = useSearchParams();
+ const paramAttribute = searchParams.get("attribute");
+ const paramAttributeInt = paramAttribute ? Number.parseInt(paramAttribute) : undefined;
+
+ /** TODO: Store selected attributes in redux */
+ const [selectedAttributes, setSelectedAttributes] = useState(
+ paramAttributeInt ? [paramAttributeInt] : [],
+ );
+ const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split("T")[0] ?? "");
+ const [showSettings, setShowSettings] = useState(false);
+
+ const onAttributeClick = (id: number) => {
+ setSelectedAttributes((prev) => {
+ if (prev.includes(id)) {
+ return prev.filter((item) => item !== id);
+ }
+ return [...prev, id];
+ });
+ };
+
+ return (
+ <>
+
+ {t("Inspector")}
+
+ setShowSettings(false)}
+ title={t("Displayed attributes")}
+ >
+
+
+ {handlers?.map((handler) => (
+
+ ))}
+
+
+ setSelectedAttributes([])}
+ disabled={!selectedAttributes.length}
+ >
+ {t("Reset")}
+
+ setShowSettings(false)}>{t("Confirm")}
+
+
+
+ setShowSettings(true)}
+ onDateChange={setSelectedDate}
+ />
+ >
+ );
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/layout.tsx b/src/client/apps/contwatch-client/app/[lang]/layout.tsx
new file mode 100644
index 0000000..a722aa1
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/layout.tsx
@@ -0,0 +1,33 @@
+import "./globals.scss";
+
+import type { PageParams } from "@repo/types/PageProps";
+import { openSans } from "@repo/ui/fonts";
+import { NavbarLayout } from "@repo/ui/NavbarLayout";
+import type { Metadata } from "next";
+import { type PropsWithChildren, Suspense } from "react";
+
+import { Providers } from "./providers";
+
+export const metadata: Metadata = {
+ title: "ContWatch",
+ description: "Scalable system for IoT automation.",
+};
+
+export async function generateStaticParams() {
+ return [{ lang: "cs" }, { lang: "en" }];
+}
+
+export default async function RootLayout({ children, params }: PropsWithChildren) {
+ const lang = (await params).lang;
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/page.tsx b/src/client/apps/contwatch-client/app/[lang]/page.tsx
new file mode 100644
index 0000000..b92819e
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/page.tsx
@@ -0,0 +1,21 @@
+import type { PageParams } from "@repo/types/PageProps";
+import { Button } from "@repo/ui/Button";
+import { Flex } from "@repo/ui/Flex";
+import { Text } from "@repo/ui/Text";
+import { ssrTranslation } from "@repo/utils/ssrTranslation";
+
+export default async function Overview({ params }: PageParams) {
+ const lang = (await params).lang;
+ const { t } = await ssrTranslation(lang);
+
+ return (
+ <>
+
+ {t("Overview")}
+
+
+ {t("Handlers")}
+
+ >
+ );
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/providers.tsx b/src/client/apps/contwatch-client/app/[lang]/providers.tsx
new file mode 100644
index 0000000..4fb7059
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/providers.tsx
@@ -0,0 +1,21 @@
+import { StoreProvider } from "@repo/store/StoreProvider";
+// import { ThemeProvider } from "next-themes";
+import type { PropsWithChildren } from "react";
+
+export function Providers({ children, lang }: PropsWithChildren<{ lang: string }>) {
+ return (
+ //
+ // TODO: Disable devTools in production
+
+ {children}
+
+ //
+ );
+}
diff --git a/src/client/apps/contwatch-client/app/[lang]/swrUtils.ts b/src/client/apps/contwatch-client/app/[lang]/swrUtils.ts
new file mode 100644
index 0000000..34b6964
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/[lang]/swrUtils.ts
@@ -0,0 +1,13 @@
+"use client";
+import type { AttributeChartModel } from "@repo/types/AttributeChartModel";
+import { getJson } from "@repo/utils/communication";
+import { Endpoint } from "@repo/utils/endpoints";
+import { getApiEndpoint } from "@repo/utils/getApiEndpoint";
+import useSWR from "swr";
+
+export const useAttributeChart = (attributeIds: number[], date?: string) => {
+ return useSWR(
+ `${getApiEndpoint(Endpoint.attributeChart)}/${attributeIds.join(",")}/${attributeIds.length > 0 ? (date ?? "") : ""}`,
+ getJson,
+ );
+};
diff --git a/src/client/apps/contwatch-client/app/favicon.ico b/src/client/apps/contwatch-client/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/src/client/apps/contwatch-client/app/favicon.ico differ
diff --git a/src/client/apps/contwatch-client/app/server/revalidate/route.ts b/src/client/apps/contwatch-client/app/server/revalidate/route.ts
new file mode 100644
index 0000000..18eb3ba
--- /dev/null
+++ b/src/client/apps/contwatch-client/app/server/revalidate/route.ts
@@ -0,0 +1,6 @@
+export async function POST(request: Request) {
+ const body = await request.json();
+ // console.log(body);
+ // revalidateTag("api"); // TODO: Fetching cache with revalidations
+ return Response.json({ revalidated: body.tag });
+}
diff --git a/src/client/apps/contwatch-client/eslint.config.js b/src/client/apps/contwatch-client/eslint.config.js
new file mode 100644
index 0000000..e8759ff
--- /dev/null
+++ b/src/client/apps/contwatch-client/eslint.config.js
@@ -0,0 +1,4 @@
+import { nextJsConfig } from "@repo/eslint-config/next-js";
+
+/** @type {import("eslint").Linter.Config} */
+export default nextJsConfig;
diff --git a/src/client/apps/contwatch-client/example.env.prod b/src/client/apps/contwatch-client/example.env.prod
new file mode 100644
index 0000000..8e4e620
--- /dev/null
+++ b/src/client/apps/contwatch-client/example.env.prod
@@ -0,0 +1,3 @@
+NEXT_PUBLIC_API_SERVER_HOST="cw.smerivo.cz"
+NEXT_PUBLIC_API_SERVER_PORT="443"
+NEXT_PUBLIC_API_SERVER_PROTOCOL="https"
diff --git a/src/client/apps/contwatch-client/middleware.ts b/src/client/apps/contwatch-client/middleware.ts
new file mode 100644
index 0000000..223351c
--- /dev/null
+++ b/src/client/apps/contwatch-client/middleware.ts
@@ -0,0 +1,35 @@
+import Negotiator from "negotiator";
+import { type NextRequest, NextResponse } from "next/server";
+
+const locales = ["en", "cs"];
+
+function getLocale(request: NextRequest) {
+ const languageHeaders = request.headers.get("Accept-Language");
+ if (!languageHeaders) return "en";
+
+ return new Negotiator({
+ headers: { "accept-language": languageHeaders },
+ }).language(locales);
+}
+
+export function middleware(request: NextRequest) {
+ // Check if there is any supported locale in the pathname
+ const { pathname } = request.nextUrl;
+ const pathnameHasLocale = locales.some(
+ (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`,
+ );
+
+ if (pathnameHasLocale) return;
+
+ const locale = getLocale(request);
+ request.nextUrl.pathname = `/${locale}${pathname}`;
+
+ return NextResponse.rewrite(request.nextUrl);
+}
+
+export const config = {
+ matcher: [
+ // Skip all internal paths
+ "/((?!_next|api|server|socket.io|.*\\.).*)",
+ ],
+};
diff --git a/src/client/apps/contwatch-client/next.config.mjs b/src/client/apps/contwatch-client/next.config.mjs
new file mode 100644
index 0000000..ed2c8f6
--- /dev/null
+++ b/src/client/apps/contwatch-client/next.config.mjs
@@ -0,0 +1,25 @@
+/** @type {import('next').NextConfig} */
+import { HOST, PORT, PROTOCOL } from "./src/settings.mjs";
+
+const nextConfig = {
+ output: "standalone",
+ eslint: {
+ ignoreDuringBuilds: true,
+ },
+ async rewrites() {
+ return {
+ afterFiles: [
+ {
+ source: "/socket.io/:path*",
+ destination: `${PROTOCOL}://${HOST}:${PORT}/socket.io/:path*/`,
+ },
+ {
+ source: "/api/:path*",
+ destination: `${PROTOCOL}://${HOST}:${PORT}/api/:path*/`,
+ },
+ ],
+ };
+ },
+};
+
+export default nextConfig;
diff --git a/src/client/apps/contwatch-client/package.json b/src/client/apps/contwatch-client/package.json
new file mode 100644
index 0000000..5cb1e39
--- /dev/null
+++ b/src/client/apps/contwatch-client/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "contwatch-client",
+ "version": "0.0.0",
+ "type": "module",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --turbopack",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint --max-warnings 0",
+ "stylelint": "stylelint \"**/*.scss\"",
+ "check-types": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/modifiers": "^9.0.0",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
+ "@repo/store": "workspace:*",
+ "@repo/types": "workspace:*",
+ "@repo/ui": "workspace:*",
+ "@repo/utils": "workspace:*",
+ "@xyflow/react": "^12.3.6",
+ "@xyflow/system": "^0.0.47",
+ "chart.js": "^4.5.0",
+ "chartjs-adapter-date-fns": "^3.0.0",
+ "chartjs-plugin-zoom": "^2.2.0",
+ "luxon": "^3.5.0",
+ "negotiator": "^1.0.0",
+ "react-chartjs-2": "^5.3.0",
+ "socket.io-client": "^4.8.1"
+ },
+ "devDependencies": {
+ "@repo/eslint-config": "workspace:*",
+ "@repo/typescript-config": "workspace:*",
+ "@types/luxon": "^3.4.2",
+ "@types/negotiator": "^0.6.3"
+ }
+}
diff --git a/src/client/apps/contwatch-client/public/icons/arrow-down-square.svg b/src/client/apps/contwatch-client/public/icons/arrow-down-square.svg
new file mode 100644
index 0000000..892009c
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/arrow-down-square.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/arrow-left.svg b/src/client/apps/contwatch-client/public/icons/arrow-left.svg
new file mode 100644
index 0000000..579c1f9
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/arrow-left.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/arrow-maximize.svg b/src/client/apps/contwatch-client/public/icons/arrow-maximize.svg
new file mode 100644
index 0000000..cf7352b
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/arrow-maximize.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/arrow-minimize.svg b/src/client/apps/contwatch-client/public/icons/arrow-minimize.svg
new file mode 100644
index 0000000..244cf0f
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/arrow-minimize.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/arrow-right-down.svg b/src/client/apps/contwatch-client/public/icons/arrow-right-down.svg
new file mode 100644
index 0000000..512508b
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/arrow-right-down.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/arrow-right-up.svg b/src/client/apps/contwatch-client/public/icons/arrow-right-up.svg
new file mode 100644
index 0000000..1b3cf20
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/arrow-right-up.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/arrow-right.svg b/src/client/apps/contwatch-client/public/icons/arrow-right.svg
new file mode 100644
index 0000000..e1bcea7
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/arrow-right.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/arrow-up-square.svg b/src/client/apps/contwatch-client/public/icons/arrow-up-square.svg
new file mode 100644
index 0000000..e808086
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/arrow-up-square.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/batery-medium.svg b/src/client/apps/contwatch-client/public/icons/batery-medium.svg
new file mode 100644
index 0000000..7815fe0
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/batery-medium.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/branch-horizontal.svg b/src/client/apps/contwatch-client/public/icons/branch-horizontal.svg
new file mode 100644
index 0000000..b1bed09
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/branch-horizontal.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/chart-square.svg b/src/client/apps/contwatch-client/public/icons/chart-square.svg
new file mode 100644
index 0000000..983790d
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/chart-square.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/chevron-down.svg b/src/client/apps/contwatch-client/public/icons/chevron-down.svg
new file mode 100644
index 0000000..3ca31a7
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/chevron-down.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/circle.svg b/src/client/apps/contwatch-client/public/icons/circle.svg
new file mode 100644
index 0000000..38c1cc7
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/circle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/cross-small.svg b/src/client/apps/contwatch-client/public/icons/cross-small.svg
new file mode 100644
index 0000000..86c18d6
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/cross-small.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/cross.svg b/src/client/apps/contwatch-client/public/icons/cross.svg
new file mode 100644
index 0000000..f3c876e
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/cross.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/current.svg b/src/client/apps/contwatch-client/public/icons/current.svg
new file mode 100644
index 0000000..dded820
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/current.svg
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/modules/web_server/static/images/icons/types/bms_serial.png b/src/client/apps/contwatch-client/public/icons/custom/battery.png
similarity index 100%
rename from modules/web_server/static/images/icons/types/bms_serial.png
rename to src/client/apps/contwatch-client/public/icons/custom/battery.png
diff --git a/modules/web_server/static/images/icons/types/http.png b/src/client/apps/contwatch-client/public/icons/custom/http.png
similarity index 100%
rename from modules/web_server/static/images/icons/types/http.png
rename to src/client/apps/contwatch-client/public/icons/custom/http.png
diff --git a/src/client/apps/contwatch-client/public/icons/custom/inverter.png b/src/client/apps/contwatch-client/public/icons/custom/inverter.png
new file mode 100644
index 0000000..3dbb735
Binary files /dev/null and b/src/client/apps/contwatch-client/public/icons/custom/inverter.png differ
diff --git a/modules/web_server/static/images/icons/types/serial.png b/src/client/apps/contwatch-client/public/icons/custom/serial.png
similarity index 100%
rename from modules/web_server/static/images/icons/types/serial.png
rename to src/client/apps/contwatch-client/public/icons/custom/serial.png
diff --git a/src/client/apps/contwatch-client/public/icons/edit-square.svg b/src/client/apps/contwatch-client/public/icons/edit-square.svg
new file mode 100644
index 0000000..822b825
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/edit-square.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/grid-mixed.svg b/src/client/apps/contwatch-client/public/icons/grid-mixed.svg
new file mode 100644
index 0000000..fa49f35
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/grid-mixed.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/humidity.svg b/src/client/apps/contwatch-client/public/icons/humidity.svg
new file mode 100644
index 0000000..5355e58
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/humidity.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/client/apps/contwatch-client/public/icons/lightbulb.svg b/src/client/apps/contwatch-client/public/icons/lightbulb.svg
new file mode 100644
index 0000000..672962f
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/lightbulb.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/menu.svg b/src/client/apps/contwatch-client/public/icons/menu.svg
new file mode 100644
index 0000000..9c5de85
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/menu.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/minus.svg b/src/client/apps/contwatch-client/public/icons/minus.svg
new file mode 100644
index 0000000..1fb4868
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/minus.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/plus.svg b/src/client/apps/contwatch-client/public/icons/plus.svg
new file mode 100644
index 0000000..a9b85f0
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/plus.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/power.svg b/src/client/apps/contwatch-client/public/icons/power.svg
new file mode 100644
index 0000000..a849cbf
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/power.svg
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/powerplug.svg b/src/client/apps/contwatch-client/public/icons/powerplug.svg
new file mode 100644
index 0000000..3c9c440
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/powerplug.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/processor.svg b/src/client/apps/contwatch-client/public/icons/processor.svg
new file mode 100644
index 0000000..db86ddd
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/processor.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/search-trending-up.svg b/src/client/apps/contwatch-client/public/icons/search-trending-up.svg
new file mode 100644
index 0000000..be20528
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/search-trending-up.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/temperature.svg b/src/client/apps/contwatch-client/public/icons/temperature.svg
new file mode 100644
index 0000000..b593899
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/temperature.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/src/client/apps/contwatch-client/public/icons/trash.svg b/src/client/apps/contwatch-client/public/icons/trash.svg
new file mode 100644
index 0000000..3572f38
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/trash.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/voltage.svg b/src/client/apps/contwatch-client/public/icons/voltage.svg
new file mode 100644
index 0000000..883cb7e
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/voltage.svg
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/wrench.svg b/src/client/apps/contwatch-client/public/icons/wrench.svg
new file mode 100644
index 0000000..aa49b30
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/wrench.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/contwatch-client/public/icons/zoom-out.svg b/src/client/apps/contwatch-client/public/icons/zoom-out.svg
new file mode 100644
index 0000000..ce4efcb
--- /dev/null
+++ b/src/client/apps/contwatch-client/public/icons/zoom-out.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/modules/web_server/static/images/favicon.png b/src/client/apps/contwatch-client/public/logo.png
similarity index 100%
rename from modules/web_server/static/images/favicon.png
rename to src/client/apps/contwatch-client/public/logo.png
diff --git a/src/client/apps/contwatch-client/src/settings.mjs b/src/client/apps/contwatch-client/src/settings.mjs
new file mode 100644
index 0000000..f9403d3
--- /dev/null
+++ b/src/client/apps/contwatch-client/src/settings.mjs
@@ -0,0 +1,3 @@
+export const HOST = process.env.NEXT_PUBLIC_API_SERVER_HOST ?? "localhost";
+export const PORT = process.env.NEXT_PUBLIC_API_SERVER_PORT ?? "5000";
+export const PROTOCOL = process.env.NEXT_PUBLIC_API_SERVER_PROTOCOL ?? "http";
diff --git a/src/client/apps/contwatch-client/src/utils.ts b/src/client/apps/contwatch-client/src/utils.ts
new file mode 100644
index 0000000..6f33226
--- /dev/null
+++ b/src/client/apps/contwatch-client/src/utils.ts
@@ -0,0 +1,5 @@
+import { jsonFetcherFactory } from "swr-models";
+
+import { HOST, PORT, PROTOCOL } from "./settings.mjs";
+
+export const fetchJson = jsonFetcherFactory(PROTOCOL, HOST, PORT, "force-cache", 600);
diff --git a/src/client/apps/contwatch-client/tsconfig.json b/src/client/apps/contwatch-client/tsconfig.json
new file mode 100644
index 0000000..b3afb74
--- /dev/null
+++ b/src/client/apps/contwatch-client/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "extends": "@repo/typescript-config/nextjs.json",
+ "compilerOptions": {
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ "next-env.d.ts",
+ "next.config.mjs",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/src/client/apps/docs/.gitignore b/src/client/apps/docs/.gitignore
new file mode 100644
index 0000000..f886745
--- /dev/null
+++ b/src/client/apps/docs/.gitignore
@@ -0,0 +1,36 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# env files (can opt-in for commiting if needed)
+.env*
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
diff --git a/src/client/apps/docs/README.md b/src/client/apps/docs/README.md
new file mode 100644
index 0000000..a98bfa8
--- /dev/null
+++ b/src/client/apps/docs/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load Inter, a custom Google Font.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
diff --git a/src/client/apps/docs/app/favicon.ico b/src/client/apps/docs/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/src/client/apps/docs/app/favicon.ico differ
diff --git a/src/client/apps/docs/app/fonts/GeistMonoVF.woff b/src/client/apps/docs/app/fonts/GeistMonoVF.woff
new file mode 100644
index 0000000..f2ae185
Binary files /dev/null and b/src/client/apps/docs/app/fonts/GeistMonoVF.woff differ
diff --git a/src/client/apps/docs/app/fonts/GeistVF.woff b/src/client/apps/docs/app/fonts/GeistVF.woff
new file mode 100644
index 0000000..1b62daa
Binary files /dev/null and b/src/client/apps/docs/app/fonts/GeistVF.woff differ
diff --git a/src/client/apps/docs/app/globals.css b/src/client/apps/docs/app/globals.css
new file mode 100644
index 0000000..3a2429b
--- /dev/null
+++ b/src/client/apps/docs/app/globals.css
@@ -0,0 +1,50 @@
+:root {
+ --background: #ffffff;
+ --foreground: #171717;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --background: #0a0a0a;
+ --foreground: #ededed;
+ }
+}
+
+html,
+body {
+ max-width: 100vw;
+ overflow-x: hidden;
+}
+
+body {
+ color: var(--foreground);
+ background: var(--background);
+}
+
+* {
+ box-sizing: border-box;
+ padding: 0;
+ margin: 0;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.imgDark {
+ display: none;
+}
+
+@media (prefers-color-scheme: dark) {
+ html {
+ color-scheme: dark;
+ }
+
+ .imgLight {
+ display: none;
+ }
+ .imgDark {
+ display: unset;
+ }
+}
diff --git a/src/client/apps/docs/app/layout.tsx b/src/client/apps/docs/app/layout.tsx
new file mode 100644
index 0000000..0283094
--- /dev/null
+++ b/src/client/apps/docs/app/layout.tsx
@@ -0,0 +1,30 @@
+import "./globals.css";
+
+import type { Metadata } from "next";
+import localFont from "next/font/local";
+
+const geistSans = localFont({
+ src: "./fonts/GeistVF.woff",
+ variable: "--font-geist-sans",
+});
+const geistMono = localFont({
+ src: "./fonts/GeistMonoVF.woff",
+ variable: "--font-geist-mono",
+});
+
+export const metadata: Metadata = {
+ title: "Create Next App",
+ description: "Generated by create next app",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/client/apps/docs/app/page.module.css b/src/client/apps/docs/app/page.module.css
new file mode 100644
index 0000000..09961ca
--- /dev/null
+++ b/src/client/apps/docs/app/page.module.css
@@ -0,0 +1,188 @@
+.page {
+ --gray-rgb: 0, 0, 0;
+ --gray-alpha-200: rgba(var(--gray-rgb), 0.08);
+ --gray-alpha-100: rgba(var(--gray-rgb), 0.05);
+
+ --button-primary-hover: #383838;
+ --button-secondary-hover: #f2f2f2;
+
+ display: grid;
+ grid-template-rows: 20px 1fr 20px;
+ align-items: center;
+ justify-items: center;
+ min-height: 100svh;
+ padding: 80px;
+ gap: 64px;
+ font-synthesis: none;
+}
+
+@media (prefers-color-scheme: dark) {
+ .page {
+ --gray-rgb: 255, 255, 255;
+ --gray-alpha-200: rgba(var(--gray-rgb), 0.145);
+ --gray-alpha-100: rgba(var(--gray-rgb), 0.06);
+
+ --button-primary-hover: #ccc;
+ --button-secondary-hover: #1a1a1a;
+ }
+}
+
+.main {
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+ grid-row-start: 2;
+}
+
+.main ol {
+ /*font-family: var(--font-geist-mono);*/
+ padding-left: 0;
+ margin: 0;
+ font-size: 14px;
+ line-height: 24px;
+ letter-spacing: -0.01em;
+ list-style-position: inside;
+}
+
+.main li:not(:last-of-type) {
+ margin-bottom: 8px;
+}
+
+.main code {
+ font-family: inherit;
+ background: var(--gray-alpha-100);
+ padding: 2px 4px;
+ border-radius: 4px;
+ font-weight: 600;
+}
+
+.ctas {
+ display: flex;
+ gap: 16px;
+}
+
+.ctas a {
+ appearance: none;
+ border-radius: 128px;
+ height: 48px;
+ padding: 0 20px;
+ border: none;
+ /*font-family: var(--font-geist-sans);*/
+ border: 1px solid transparent;
+ transition: background 0.2s, color 0.2s, border-color 0.2s;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ line-height: 20px;
+ font-weight: 500;
+}
+
+a.primary {
+ background: var(--foreground);
+ color: var(--background);
+ gap: 8px;
+}
+
+a.secondary {
+ border-color: var(--gray-alpha-200);
+ min-width: 180px;
+}
+
+button.secondary {
+ appearance: none;
+ border-radius: 128px;
+ height: 48px;
+ padding: 0 20px;
+ border: none;
+ /*font-family: var(--font-geist-sans);*/
+ border: 1px solid transparent;
+ transition: background 0.2s, color 0.2s, border-color 0.2s;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ line-height: 20px;
+ font-weight: 500;
+ background: transparent;
+ border-color: var(--gray-alpha-200);
+ min-width: 180px;
+}
+
+.footer {
+ /*font-family: var(--font-geist-sans);*/
+ grid-row-start: 3;
+ display: flex;
+ gap: 24px;
+}
+
+.footer a {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.footer img {
+ flex-shrink: 0;
+}
+
+/* Enable hover only on non-touch devices */
+@media (hover: hover) and (pointer: fine) {
+ a.primary:hover {
+ background: var(--button-primary-hover);
+ border-color: transparent;
+ }
+
+ a.secondary:hover {
+ background: var(--button-secondary-hover);
+ border-color: transparent;
+ }
+
+ .footer a:hover {
+ text-decoration: underline;
+ text-underline-offset: 4px;
+ }
+}
+
+@media (max-width: 600px) {
+ .page {
+ padding: 32px;
+ padding-bottom: 80px;
+ }
+
+ .main {
+ align-items: center;
+ }
+
+ .main ol {
+ text-align: center;
+ }
+
+ .ctas {
+ flex-direction: column;
+ }
+
+ .ctas a {
+ font-size: 14px;
+ height: 40px;
+ padding: 0 16px;
+ }
+
+ a.secondary {
+ min-width: auto;
+ }
+
+ .footer {
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ .logo {
+ filter: invert();
+ }
+}
diff --git a/src/client/apps/docs/app/page.tsx b/src/client/apps/docs/app/page.tsx
new file mode 100644
index 0000000..58027de
--- /dev/null
+++ b/src/client/apps/docs/app/page.tsx
@@ -0,0 +1,87 @@
+import Image, { type ImageProps } from "next/image";
+
+import styles from "./page.module.css";
+
+type Props = Omit & {
+ srcLight: string;
+ srcDark: string;
+};
+
+const ThemeImage = (props: Props) => {
+ const { srcLight, srcDark, ...rest } = props;
+
+ return (
+ <>
+
+
+ >
+ );
+};
+
+export default function Home() {
+ return (
+
+
+
+
+
+ Get started by editing apps/docs/app/page.tsx
+
+ Save and see your changes instantly.
+
+
+
+
+
+
+ );
+}
diff --git a/src/client/apps/docs/eslint.config.js b/src/client/apps/docs/eslint.config.js
new file mode 100644
index 0000000..e8759ff
--- /dev/null
+++ b/src/client/apps/docs/eslint.config.js
@@ -0,0 +1,4 @@
+import { nextJsConfig } from "@repo/eslint-config/next-js";
+
+/** @type {import("eslint").Linter.Config} */
+export default nextJsConfig;
diff --git a/src/client/apps/docs/next.config.js b/src/client/apps/docs/next.config.js
new file mode 100644
index 0000000..4678774
--- /dev/null
+++ b/src/client/apps/docs/next.config.js
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+export default nextConfig;
diff --git a/src/client/apps/docs/package.json b/src/client/apps/docs/package.json
new file mode 100644
index 0000000..1189fcb
--- /dev/null
+++ b/src/client/apps/docs/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "docs",
+ "version": "0.1.0",
+ "type": "module",
+ "private": true,
+ "scripts": {
+ "dev": "next dev --turbopack --port 3001",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint --max-warnings 0",
+ "check-types": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@repo/ui": "workspace:*"
+ },
+ "devDependencies": {
+ "@repo/eslint-config": "workspace:*",
+ "@repo/typescript-config": "workspace:*",
+ "@types/node": "^22",
+ "typescript": "^5.5.4"
+ }
+}
diff --git a/src/client/apps/docs/public/file-text.svg b/src/client/apps/docs/public/file-text.svg
new file mode 100644
index 0000000..9cfb3c9
--- /dev/null
+++ b/src/client/apps/docs/public/file-text.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/docs/public/globe.svg b/src/client/apps/docs/public/globe.svg
new file mode 100644
index 0000000..4230a3d
--- /dev/null
+++ b/src/client/apps/docs/public/globe.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/client/apps/docs/public/next.svg b/src/client/apps/docs/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/src/client/apps/docs/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/client/apps/docs/public/turborepo-dark.svg b/src/client/apps/docs/public/turborepo-dark.svg
new file mode 100644
index 0000000..dae38fe
--- /dev/null
+++ b/src/client/apps/docs/public/turborepo-dark.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/client/apps/docs/public/turborepo-light.svg b/src/client/apps/docs/public/turborepo-light.svg
new file mode 100644
index 0000000..ddea915
--- /dev/null
+++ b/src/client/apps/docs/public/turborepo-light.svg
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/client/apps/docs/public/vercel.svg b/src/client/apps/docs/public/vercel.svg
new file mode 100644
index 0000000..0164ddc
--- /dev/null
+++ b/src/client/apps/docs/public/vercel.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/client/apps/docs/public/window.svg b/src/client/apps/docs/public/window.svg
new file mode 100644
index 0000000..bbc7800
--- /dev/null
+++ b/src/client/apps/docs/public/window.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/client/apps/docs/tsconfig.json b/src/client/apps/docs/tsconfig.json
new file mode 100644
index 0000000..7aef056
--- /dev/null
+++ b/src/client/apps/docs/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "extends": "@repo/typescript-config/nextjs.json",
+ "compilerOptions": {
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ]
+ },
+ "include": [
+ "**/*.ts",
+ "**/*.tsx",
+ "next-env.d.ts",
+ "next.config.js",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/src/client/biome.json b/src/client/biome.json
new file mode 100644
index 0000000..4da0b59
--- /dev/null
+++ b/src/client/biome.json
@@ -0,0 +1,68 @@
+{
+ "$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
+ "vcs": {
+ "enabled": false,
+ "clientKind": "git",
+ "useIgnoreFile": false
+ },
+ "files": {
+ "ignoreUnknown": false,
+ "includes": ["**/*.ts", "**/*.tsx", "**/*.md", "!**/.next"]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space",
+ "indentWidth": 4,
+ "lineWidth": 110
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true,
+ "correctness": {
+ "noUnusedImports": {
+ "level": "error",
+ "fix": "safe",
+ "options": {}
+ },
+ "useParseIntRadix": "off"
+ },
+ "suspicious": {
+ "noConfusingVoidType": "off",
+ "noExplicitAny": "off"
+ },
+ "style": {
+ "useConsistentCurlyBraces": {
+ "level": "off",
+ "fix": "safe",
+ "options": {}
+ }
+ }
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "double"
+ }
+ },
+ "assist": {
+ "actions": {
+ "source": {
+ "organizeImports": {
+ "level": "off",
+ "options": {
+ "groups": [
+ ":URL",
+ ":BLANK_LINE:",
+ ["**", "!@repo/**", "!./**", "!../**"],
+ ":BLANK_LINE:",
+ "@repo/**",
+ ":BLANK_LINE:",
+ ["./**", "../**"]
+ ]
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/client/package.json b/src/client/package.json
new file mode 100644
index 0000000..d944abd
--- /dev/null
+++ b/src/client/package.json
@@ -0,0 +1,64 @@
+{
+ "name": "contwatch-turborepo",
+ "private": true,
+ "scripts": {
+ "preinstall": "npx only-allow pnpm",
+ "build": "turbo build",
+ "dev": "turbo dev",
+ "lint": "turbo lint",
+ "format": "prettier --write \"**/*.{ts,tsx,md}\"",
+ "stylelint": "turbo stylelint",
+ "biome": "biome check",
+ "test": "turbo test",
+ "ci": "turbo lint stylelint build test && npm run biome",
+ "screen": "cd apps/contwatch-client && npm run build && screen -S contwatch-client -m npm run start"
+ },
+ "dependencies": {
+ "@reduxjs/toolkit": "^2.5.0",
+ "next-redux-wrapper": "^8.1.0",
+ "lodash.debounce": "^4.0.8",
+ "next": "^15.5.7",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-redux": "^9.2.0",
+ "sass": "^1.83.0",
+ "swr": "^2.3.8",
+ "swr-models": "^1.0.2",
+ "turbo": "^2.5.8",
+ "typescript": "^5.9.3"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "^2.3.8",
+ "@eslint/js": "^9.17.0",
+ "@next/eslint-plugin-next": "^15.1.0",
+ "@types/jest": "^29.5.14",
+ "@types/lodash.debounce": "^4.0.9",
+ "@types/react": "^19.0.2",
+ "@types/react-dom": "^19.0.2",
+ "@typescript-eslint/eslint-plugin": "^8.15.0",
+ "@typescript-eslint/parser": "^8.15.0",
+ "eslint": "^9.39.1",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-only-warn": "^1.1.0",
+ "eslint-plugin-react": "^7.37.5",
+ "eslint-plugin-react-hooks": "^7.0.1",
+ "eslint-plugin-simple-import-sort": "^12.1.1",
+ "eslint-plugin-turbo": "^2.6.3",
+ "globals": "^15.12.0",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
+ "only-allow": "^1.2.2",
+ "prettier": "^3.4.2",
+ "stylelint": "^16.10.0",
+ "stylelint-config-prettier-scss": "^1.0.0",
+ "stylelint-config-property-sort-order-smacss": "^10.0.0",
+ "stylelint-config-standard-scss": "^13.1.0",
+ "stylelint-order": "^6.0.4",
+ "ts-jest": "^29.2.5",
+ "typescript-eslint": "^8.49.0"
+ },
+ "engines": {
+ "node": ">=22"
+ },
+ "packageManager": "pnpm@10.0.0"
+}
diff --git a/src/client/packages/eslint-config/README.md b/src/client/packages/eslint-config/README.md
new file mode 100644
index 0000000..8b42d90
--- /dev/null
+++ b/src/client/packages/eslint-config/README.md
@@ -0,0 +1,3 @@
+# `@turbo/eslint-config`
+
+Collection of internal eslint configurations.
diff --git a/src/client/packages/eslint-config/base.js b/src/client/packages/eslint-config/base.js
new file mode 100644
index 0000000..9a5916e
--- /dev/null
+++ b/src/client/packages/eslint-config/base.js
@@ -0,0 +1,36 @@
+import js from "@eslint/js";
+import eslintConfigPrettier from "eslint-config-prettier";
+import onlyWarn from "eslint-plugin-only-warn";
+import simpleImportSort from "eslint-plugin-simple-import-sort";
+import turboPlugin from "eslint-plugin-turbo";
+import tseslint from "typescript-eslint";
+
+/**
+ * A shared ESLint configuration for the repository.
+ *
+ * @type {import("eslint").Linter.Config}
+ * */
+export const config = [
+ js.configs.recommended,
+ eslintConfigPrettier,
+ ...tseslint.configs.recommended,
+ {
+ plugins: {
+ turbo: turboPlugin,
+ "simple-import-sort": simpleImportSort,
+ },
+ rules: {
+ "turbo/no-undeclared-env-vars": "warn",
+ "simple-import-sort/imports": "error",
+ "simple-import-sort/exports": "error",
+ },
+ },
+ {
+ plugins: {
+ onlyWarn,
+ },
+ },
+ {
+ ignores: ["dist/**"],
+ },
+];
diff --git a/src/client/packages/eslint-config/next.js b/src/client/packages/eslint-config/next.js
new file mode 100644
index 0000000..f186b41
--- /dev/null
+++ b/src/client/packages/eslint-config/next.js
@@ -0,0 +1,50 @@
+import js from "@eslint/js";
+import pluginNext from "@next/eslint-plugin-next";
+import eslintConfigPrettier from "eslint-config-prettier";
+import pluginReact from "eslint-plugin-react";
+import pluginReactHooks from "eslint-plugin-react-hooks";
+import globals from "globals";
+import tseslint from "typescript-eslint";
+import { config as baseConfig } from "./base.js";
+
+/**
+ * A custom ESLint configuration for libraries that use Next.js.
+ *
+ * @type {import("eslint").Linter.Config}
+ * */
+export const nextJsConfig = [
+ ...baseConfig,
+ js.configs.recommended,
+ eslintConfigPrettier,
+ ...tseslint.configs.recommended,
+ {
+ ...pluginReact.configs.flat.recommended,
+ languageOptions: {
+ ...pluginReact.configs.flat.recommended.languageOptions,
+ globals: {
+ ...globals.serviceworker,
+ ...globals.node,
+ },
+ },
+ },
+ {
+ plugins: {
+ "@next/next": pluginNext,
+ },
+ rules: {
+ ...pluginNext.configs.recommended.rules,
+ ...pluginNext.configs["core-web-vitals"].rules,
+ },
+ },
+ {
+ plugins: {
+ "react-hooks": pluginReactHooks,
+ },
+ settings: { react: { version: "detect" } },
+ rules: {
+ ...pluginReactHooks.configs.recommended.rules,
+ // React scope no longer necessary with new JSX transform.
+ "react/react-in-jsx-scope": "off",
+ },
+ },
+];
diff --git a/src/client/packages/eslint-config/package.json b/src/client/packages/eslint-config/package.json
new file mode 100644
index 0000000..fa81a6a
--- /dev/null
+++ b/src/client/packages/eslint-config/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "@repo/eslint-config",
+ "version": "0.0.0",
+ "type": "module",
+ "private": true,
+ "exports": {
+ "./base": "./base.js",
+ "./next-js": "./next.js",
+ "./react-internal": "./react-internal.js"
+ }
+}
diff --git a/src/client/packages/eslint-config/react-internal.js b/src/client/packages/eslint-config/react-internal.js
new file mode 100644
index 0000000..4962db6
--- /dev/null
+++ b/src/client/packages/eslint-config/react-internal.js
@@ -0,0 +1,39 @@
+import js from "@eslint/js";
+import eslintConfigPrettier from "eslint-config-prettier";
+import pluginReact from "eslint-plugin-react";
+import pluginReactHooks from "eslint-plugin-react-hooks";
+import globals from "globals";
+import tseslint from "typescript-eslint";
+import { config as baseConfig } from "./base.js";
+
+/**
+ * A custom ESLint configuration for libraries that use React.
+ *
+ * @type {import("eslint").Linter.Config} */
+export const config = [
+ ...baseConfig,
+ js.configs.recommended,
+ eslintConfigPrettier,
+ ...tseslint.configs.recommended,
+ pluginReact.configs.flat.recommended,
+ {
+ languageOptions: {
+ ...pluginReact.configs.flat.recommended.languageOptions,
+ globals: {
+ ...globals.serviceworker,
+ ...globals.browser,
+ },
+ },
+ },
+ {
+ plugins: {
+ "react-hooks": pluginReactHooks,
+ },
+ settings: { react: { version: "detect" } },
+ rules: {
+ ...pluginReactHooks.configs.recommended.rules,
+ // React scope no longer necessary with new JSX transform.
+ "react/react-in-jsx-scope": "off",
+ },
+ },
+];
diff --git a/src/client/packages/store/eslint.config.js b/src/client/packages/store/eslint.config.js
new file mode 100644
index 0000000..19170f8
--- /dev/null
+++ b/src/client/packages/store/eslint.config.js
@@ -0,0 +1,4 @@
+import { config } from "@repo/eslint-config/react-internal";
+
+/** @type {import("eslint").Linter.Config} */
+export default config;
diff --git a/src/client/packages/store/package.json b/src/client/packages/store/package.json
new file mode 100644
index 0000000..99d473f
--- /dev/null
+++ b/src/client/packages/store/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "@repo/store",
+ "version": "1.0.0",
+ "type": "module",
+ "private": true,
+ "dependencies": {
+ "@repo/types": "workspace:*"
+ },
+ "devDependencies": {
+ "@repo/eslint-config": "workspace:*"
+ },
+ "exports": {
+ "./store": "./src/store.ts",
+ "./StoreProvider": "./src/components/StoreProvider.tsx",
+ "./hooks/useLocalization": "./src/hooks/useLocalization.ts",
+ "./slices/settingsSlice": "./src/slices/settingsSlice.ts"
+ },
+ "scripts": {
+ "lint": "eslint . --max-warnings 0 --cache"
+ }
+}
diff --git a/src/client/packages/store/src/components/StoreProvider.tsx b/src/client/packages/store/src/components/StoreProvider.tsx
new file mode 100644
index 0000000..69d5231
--- /dev/null
+++ b/src/client/packages/store/src/components/StoreProvider.tsx
@@ -0,0 +1,32 @@
+"use client";
+
+import { type FC, type PropsWithChildren, useState } from "react";
+import { Provider } from "react-redux";
+
+import { setLocaleState } from "../slices/settingsSlice";
+import { makeStore } from "../store";
+
+type StoreProviderProps = PropsWithChildren<{
+ lang?: string;
+ devTools?: boolean;
+}>;
+
+export const StoreProvider: FC = ({ lang, devTools, children }) => {
+ // const storeRef = useRef(null);
+ //
+ // if (!storeRef.current) {
+ // // Create the store instance the first time this renders
+ // storeRef.current = makeStore();
+ // }
+ //
+ // storeRef.current.dispatch(setLocaleState(lang));
+
+ const [store] = useState(() => {
+ // Create the store instance the first time this renders
+ const store = makeStore(devTools);
+ store.dispatch(setLocaleState(lang));
+ return store;
+ });
+
+ return {children} ;
+};
diff --git a/src/client/packages/store/src/hooks/useLocalization.ts b/src/client/packages/store/src/hooks/useLocalization.ts
new file mode 100644
index 0000000..47d2944
--- /dev/null
+++ b/src/client/packages/store/src/hooks/useLocalization.ts
@@ -0,0 +1,56 @@
+import { useSelector } from "react-redux";
+
+import { selectLocaleState } from "../slices/settingsSlice";
+
+export enum LOCALES {
+ en_US = "en-US",
+ cs_CZ = "cs-CZ",
+}
+
+export const useLocalization = () => {
+ const currentLocale = useSelector(selectLocaleState);
+
+ /**
+ * Localize a number or string to the current or specified locale.
+ * @param value The value to localize.
+ * @param locale The locale to localize to. Defaults to the current locale.
+ */
+ const localizeValue = (value: string | number, locale?: LOCALES) => {
+ const targetLocale = locale ?? currentLocale;
+ return value.toLocaleString(targetLocale);
+ };
+
+ /**
+ * Localize a date to the current or specified locale.
+ * @param date The date to localize.
+ * @param locale The locale to localize to. Defaults to the current locale.
+ */
+ const localizeDate = (date: Date, locale?: LOCALES) => {
+ const targetLocale = locale ?? currentLocale;
+ return date.toLocaleDateString(targetLocale);
+ };
+
+ /**
+ * Localize a number to a currency in the current or specified locale.
+ * @param value The value to localize.
+ * @param currency The currency to localize to.
+ * @param locale The locale to localize to. Defaults to the current locale.
+ */
+ const localizeCurrency = (value: number = Number.NaN, currency = "CZK", locale?: LOCALES) => {
+ const result = new Intl.NumberFormat(locale ?? currentLocale, {
+ style: "currency",
+ currency,
+ trailingZeroDisplay: "stripIfInteger",
+ }).format(value);
+ if (Number.isNaN(value)) {
+ return result.replace("NaN", "");
+ }
+ return result;
+ };
+
+ return {
+ localizeValue,
+ localizeDate,
+ localizeCurrency,
+ };
+};
diff --git a/src/client/packages/store/src/slices/settingsSlice.ts b/src/client/packages/store/src/slices/settingsSlice.ts
new file mode 100644
index 0000000..176e863
--- /dev/null
+++ b/src/client/packages/store/src/slices/settingsSlice.ts
@@ -0,0 +1,42 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+// import { LOCALES } from "../localization";
+import type { AppState } from "../store";
+
+export enum LOCALES {
+ cs = "cs",
+ en = "en",
+}
+
+// Type for the state
+export interface SettingsState {
+ iconThemeState: string;
+ localeState: LOCALES;
+}
+
+// Initial state
+const initialState: SettingsState = {
+ iconThemeState: "cosmic",
+ localeState: LOCALES.cs,
+};
+
+// Actual Slice
+export const settingsSlice = createSlice({
+ name: "settings",
+ initialState,
+ reducers: {
+ setIconThemeState(state, action) {
+ state.iconThemeState = action.payload;
+ },
+ setLocaleState(state, action) {
+ state.localeState = action.payload;
+ },
+ },
+});
+
+export const { setIconThemeState, setLocaleState } = settingsSlice.actions;
+
+export const selectIconThemeState = (state: AppState) => state.settings.iconThemeState;
+export const selectLocaleState = (state: AppState) => state.settings.localeState;
+
+export default settingsSlice.reducer;
diff --git a/src/client/packages/store/src/store.ts b/src/client/packages/store/src/store.ts
new file mode 100644
index 0000000..9d765e9
--- /dev/null
+++ b/src/client/packages/store/src/store.ts
@@ -0,0 +1,15 @@
+import { type Action, configureStore, type ThunkAction } from "@reduxjs/toolkit";
+
+import { settingsSlice } from "./slices/settingsSlice";
+
+export const makeStore = (devTools = false) =>
+ configureStore({
+ reducer: {
+ [settingsSlice.name]: settingsSlice.reducer,
+ },
+ devTools: devTools,
+ });
+
+export type AppStore = ReturnType;
+export type AppState = ReturnType;
+export type AppThunk = ThunkAction;
diff --git a/src/client/packages/types/package.json b/src/client/packages/types/package.json
new file mode 100644
index 0000000..b01c125
--- /dev/null
+++ b/src/client/packages/types/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@repo/types",
+ "version": "1.0.0",
+ "type": "module",
+ "private": true,
+ "exports": {
+ "./APIModel": "./src/models/APIModel.d.ts",
+ "./AttributeModel": "./src/models/AttributeModel.d.ts",
+ "./AttributeChartModel": "./src/models/AttributeChartModel.d.ts",
+ "./DataStatModel": "./src/models/DataStatModel.d.ts",
+ "./HandlerModel": "./src/models/HandlerModel.d.ts",
+
+ "./IconType": "./src/IconType.d.ts",
+ "./PageProps": "./src/PageProps.d.ts"
+ }
+}
diff --git a/src/client/packages/types/src/IconType.d.ts b/src/client/packages/types/src/IconType.d.ts
new file mode 100644
index 0000000..7cc9959
--- /dev/null
+++ b/src/client/packages/types/src/IconType.d.ts
@@ -0,0 +1,25 @@
+export type IconType =
+ | "arrow-down-square"
+ | "arrow-left"
+ | "arrow-maximize"
+ | "arrow-minimize"
+ | "arrow-right"
+ | "arrow-right-down"
+ | "arrow-right-up"
+ | "arrow-up-square"
+ | "branch-horizontal"
+ | "chart-square"
+ | "chevron-down"
+ | "circle"
+ | "cross"
+ | "cross-small"
+ | "edit-square"
+ | "grid-mixed"
+ | "menu"
+ | "minus"
+ | "plus"
+ | "processor"
+ | "search-trending-up"
+ | "trash"
+ | "wrench"
+ | "zoom-out";
diff --git a/src/client/packages/types/src/PageProps.d.ts b/src/client/packages/types/src/PageProps.d.ts
new file mode 100644
index 0000000..d6bccf4
--- /dev/null
+++ b/src/client/packages/types/src/PageProps.d.ts
@@ -0,0 +1,3 @@
+export type PageParams = {
+ params: Promise<{ lang: string }>;
+};
diff --git a/src/client/packages/types/src/models/APIModel.d.ts b/src/client/packages/types/src/models/APIModel.d.ts
new file mode 100644
index 0000000..0d771de
--- /dev/null
+++ b/src/client/packages/types/src/models/APIModel.d.ts
@@ -0,0 +1,3 @@
+export interface APIModel {
+ id: number;
+}
diff --git a/src/client/packages/types/src/models/AttributeChartModel.d.ts b/src/client/packages/types/src/models/AttributeChartModel.d.ts
new file mode 100644
index 0000000..160130f
--- /dev/null
+++ b/src/client/packages/types/src/models/AttributeChartModel.d.ts
@@ -0,0 +1,12 @@
+type ChartPoint = {
+ x: number;
+ y: string;
+};
+
+export interface AttributeChartModel {
+ id: number;
+ label: string;
+ unit?: string;
+ color?: string;
+ data: ChartPoint[];
+}
diff --git a/src/client/packages/types/src/models/AttributeModel.d.ts b/src/client/packages/types/src/models/AttributeModel.d.ts
new file mode 100644
index 0000000..8f47bb5
--- /dev/null
+++ b/src/client/packages/types/src/models/AttributeModel.d.ts
@@ -0,0 +1,19 @@
+import type { IconType } from "../IconType";
+
+export interface AttributeModel {
+ id: number;
+ name: string;
+ handler?: number;
+ enabled?: boolean;
+ base_unit?: string;
+ label?: string;
+ icon?: IconType;
+ order?: number;
+ rounding?: number;
+ color?: string;
+ data?: {
+ value?: string | number;
+ unit?: string;
+ trend: -1 | 0 | 1;
+ };
+}
diff --git a/src/client/packages/types/src/models/DataStatModel.d.ts b/src/client/packages/types/src/models/DataStatModel.d.ts
new file mode 100644
index 0000000..8b2b4ae
--- /dev/null
+++ b/src/client/packages/types/src/models/DataStatModel.d.ts
@@ -0,0 +1,9 @@
+export interface DataStatModel {
+ id: number;
+ attribute: number;
+ type: string;
+ value: number;
+ unit: string;
+ date: string;
+ time: string;
+}
diff --git a/src/client/packages/types/src/models/HandlerModel.d.ts b/src/client/packages/types/src/models/HandlerModel.d.ts
new file mode 100644
index 0000000..1726b88
--- /dev/null
+++ b/src/client/packages/types/src/models/HandlerModel.d.ts
@@ -0,0 +1,41 @@
+import type { IconType } from "../IconType";
+
+export enum HandlerStatus {
+ DISCONNECTED = 0,
+ CONNECTED = 1,
+ DISABLED = 2,
+}
+
+export type HandlerConfig = Record;
+
+export interface HandlerOptions {
+ label?: string;
+ config: HandlerConfig;
+}
+
+export interface HandlerTypeModel {
+ type: string;
+ name: string;
+ icon: IconType;
+ configFields?: Record;
+}
+
+export interface HandlerModel extends HandlerTypeModel {
+ id: number;
+ description: string;
+ status?: HandlerStatus;
+ last_message?: number;
+ attributes: {
+ id: number;
+ name: string;
+ }[];
+ options?: HandlerOptions;
+ availableAttributes: { name: string; value: string | number }[];
+ actions: {
+ [key: string]: {
+ description: string;
+ destination: string;
+ params: Record;
+ };
+ };
+}
diff --git a/src/client/packages/typescript-config/base.json b/src/client/packages/typescript-config/base.json
new file mode 100644
index 0000000..7d000b9
--- /dev/null
+++ b/src/client/packages/typescript-config/base.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "compilerOptions": {
+ "declaration": true,
+ "declarationMap": true,
+ "esModuleInterop": true,
+ "incremental": false,
+ "isolatedModules": true,
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
+ "module": "NodeNext",
+ "moduleDetection": "force",
+ "moduleResolution": "Bundler",
+ "noUncheckedIndexedAccess": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "target": "ES2023"
+ }
+}
diff --git a/src/client/packages/typescript-config/nextjs.json b/src/client/packages/typescript-config/nextjs.json
new file mode 100644
index 0000000..e6defa4
--- /dev/null
+++ b/src/client/packages/typescript-config/nextjs.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "./base.json",
+ "compilerOptions": {
+ "plugins": [{ "name": "next" }],
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "allowJs": true,
+ "jsx": "preserve",
+ "noEmit": true
+ }
+}
diff --git a/src/client/packages/typescript-config/package.json b/src/client/packages/typescript-config/package.json
new file mode 100644
index 0000000..27c0e60
--- /dev/null
+++ b/src/client/packages/typescript-config/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@repo/typescript-config",
+ "version": "0.0.0",
+ "private": true,
+ "license": "MIT",
+ "publishConfig": {
+ "access": "public"
+ }
+}
diff --git a/src/client/packages/typescript-config/react-library.json b/src/client/packages/typescript-config/react-library.json
new file mode 100644
index 0000000..c3a1b26
--- /dev/null
+++ b/src/client/packages/typescript-config/react-library.json
@@ -0,0 +1,7 @@
+{
+ "$schema": "https://json.schemastore.org/tsconfig",
+ "extends": "./base.json",
+ "compilerOptions": {
+ "jsx": "react-jsx"
+ }
+}
diff --git a/src/client/packages/ui/eslint.config.js b/src/client/packages/ui/eslint.config.js
new file mode 100644
index 0000000..19170f8
--- /dev/null
+++ b/src/client/packages/ui/eslint.config.js
@@ -0,0 +1,4 @@
+import { config } from "@repo/eslint-config/react-internal";
+
+/** @type {import("eslint").Linter.Config} */
+export default config;
diff --git a/src/client/packages/ui/global.d.ts b/src/client/packages/ui/global.d.ts
new file mode 100644
index 0000000..d46824a
--- /dev/null
+++ b/src/client/packages/ui/global.d.ts
@@ -0,0 +1,4 @@
+declare module "*.module.scss" {
+ const classes: { [key: string]: string };
+ export default classes;
+}
diff --git a/src/client/packages/ui/jest.config.ts b/src/client/packages/ui/jest.config.ts
new file mode 100644
index 0000000..39f12da
--- /dev/null
+++ b/src/client/packages/ui/jest.config.ts
@@ -0,0 +1,8 @@
+module.exports = {
+ preset: "ts-jest",
+ testEnvironment: "jsdom",
+ setupFilesAfterEnv: ["/jest.setup.ts"],
+ moduleNameMapper: {
+ "\\.module\\.scss$": "identity-obj-proxy",
+ },
+};
diff --git a/src/client/packages/ui/jest.setup.ts b/src/client/packages/ui/jest.setup.ts
new file mode 100644
index 0000000..2984018
--- /dev/null
+++ b/src/client/packages/ui/jest.setup.ts
@@ -0,0 +1,13 @@
+// Mocking the Google font loader
+jest.mock("next/font/google", () => {
+ return new Proxy(
+ {},
+ {
+ get: () => () => ({
+ style: {
+ fontFamily: "mocked",
+ },
+ }),
+ },
+ );
+});
diff --git a/src/client/packages/ui/package.json b/src/client/packages/ui/package.json
new file mode 100644
index 0000000..48374f1
--- /dev/null
+++ b/src/client/packages/ui/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "@repo/ui",
+ "version": "0.0.0",
+ "type": "module",
+ "private": true,
+ "exports": {
+ "./Button": "./src/components/Button/Button.tsx",
+ "./Flex": "./src/components/Flex/Flex.tsx",
+ "./Icon": "./src/components/Icon/Icon.tsx",
+ "./Input": "./src/components/Input/Input.tsx",
+ "./Navbar": "./src/components/Navbar/Navbar.tsx",
+ "./NavbarItem": "./src/components/Navbar/components/NavbarItem/NavbarItem.tsx",
+ "./NavbarLayout": "./src/components/NavbarLayout/NavbarLayout.tsx",
+ "./Popup": "./src/components/Popup/Popup.tsx",
+ "./Separator": "./src/components/Separator/Separator.tsx",
+ "./Text": "./src/components/Text/Text.tsx",
+ "./FlexPartials": "./src/partials/FlexPartials/FlexPartials.tsx",
+ "./fonts": "./src/fonts.ts"
+ },
+ "scripts": {
+ "lint": "eslint . --max-warnings 0 --cache",
+ "stylelint": "stylelint \"**/*.scss\"",
+ "test": "jest",
+ "generate:component": "turbo gen react-component"
+ },
+ "dependencies": {
+ "@repo/store": "workspace:*",
+ "@repo/typescript-config": "workspace:*",
+ "@repo/utils": "workspace:*",
+ "csstype": "^3.1.3",
+ "react-spinners": "^0.15.0"
+ },
+ "devDependencies": {
+ "@repo/types": "workspace:*",
+ "@repo/eslint-config": "workspace:*",
+ "@testing-library/dom": "^10.4.0",
+ "@testing-library/jest-dom": "^6.6.3",
+ "@testing-library/react": "^16.1.0",
+ "@testing-library/user-event": "^14.5.2",
+ "@turbo/gen": "^1.12.4",
+ "@types/node": "^22",
+ "@types/redux-mock-store": "^1.5.0",
+ "identity-obj-proxy": "^3.0.0",
+ "jest": "^29.7.0",
+ "jest-environment-jsdom": "^29.7.0",
+ "redux-mock-store": "^1.5.5"
+ }
+}
diff --git a/src/client/packages/ui/src/components/Button/Button.module.scss b/src/client/packages/ui/src/components/Button/Button.module.scss
new file mode 100644
index 0000000..f2cfe76
--- /dev/null
+++ b/src/client/packages/ui/src/components/Button/Button.module.scss
@@ -0,0 +1,204 @@
+@mixin button-default {
+ --button-text-color: var(--color-mono-light);
+ --button-border-radius: var(--border-radius-small);
+ --button-background: var(--color-primary);
+ --button-font-size: var(--font-size-tiny);
+ --button-font-weight: var(--font-weight-bold);
+ --button-box-shadow: var(--box-shadow-button);
+ --button-border: none;
+ --button-padding: 13px 36px;
+ --icon-filter: var(--default-button-icon-filter);
+
+ &:hover {
+ transform: var(--transform-hover);
+ filter: brightness(1.1);
+ backdrop-filter: blur(10px) brightness(1.1);
+ }
+
+ &:active {
+ transform: scale(0.98) translateY(2px);
+ transition: var(--transition-click);
+ filter: brightness(0.9);
+ }
+
+ &:disabled {
+ transform: unset;
+ filter: grayscale(0.7);
+ backdrop-filter: none;
+ }
+}
+
+.button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: var(--button-padding);
+ transition: var(--transition-default);
+ border: var(--button-border);
+ border-radius: var(--button-border-radius);
+ background: var(--button-background);
+ box-shadow: var(--button-box-shadow);
+ color: var(--button-text-color);
+ font-family: inherit;
+ font-size: var(--button-font-size);
+ font-weight: var(--button-font-weight);
+ text-decoration: none;
+ cursor: pointer;
+ gap: 6px;
+
+ --button-box-shadow: unset;
+
+ &:disabled {
+ cursor: not-allowed;
+
+ &:hover {
+ transform: unset;
+ }
+ }
+
+ &--uppercased {
+ text-transform: uppercase;
+ }
+
+
+ &--grow {
+ flex-grow: 1;
+
+ &Mobile {
+ @media (width <= 768px) {
+ flex-grow: 1;
+ }
+ }
+ }
+
+ &--slim {
+ padding: 13px 15px;
+ }
+
+ &--variant {
+ &-red {
+ background-color: var(--color-red);
+ }
+
+ &-red,
+ &-default {
+ @include button-default;
+
+ &:hover {
+ --button-box-shadow: var(--box-shadow-button-hover);
+ }
+
+ &:disabled {
+ box-shadow: unset;
+ }
+ }
+
+ &-outline {
+ @include button-default;
+
+ --button-background: rgb(255 255 255 / 10%);
+ --button-text-color: var(--color-primary);
+ --button-box-shadow: inset 0 0 0 3px var(--color-primary), var(--box-shadow-button);
+ --icon-filter: var(--primary-color-filter);
+
+ &:hover {
+ --button-box-shadow: inset 0 0 0 3px var(--color-primary),
+ var(--box-shadow-button-hover);
+ }
+
+ &:disabled {
+ --button-box-shadow: inset 0 0 0 3px var(--color-primary);
+ }
+ }
+
+ &-card {
+ --button-border-radius: 15px;
+ --button-font-size: 16px;
+ --button-font-weight: var(--font-weight-medium);
+ --button-padding: 10px;
+
+ align-items: center;
+ justify-content: flex-start;
+ gap: 10px;
+
+ &:hover {
+ --button-background: var(--color-mono-light);
+ }
+ }
+
+ // &-white {
+ // @include button-theme;
+
+ // background: var(--white);
+ // color: var(--primary-text);
+ // }
+
+ // &-outline-white {
+ // --color-primary: var(--color-mono-light);
+ // }
+
+ // &-outline-small {
+ // padding: 12px 20px !important;
+ // font-size: 13px !important;
+ // }
+
+ // &-input-control {
+ // align-self: center;
+ // margin: 0 10px;
+ // padding: 0;
+ // border-radius: 5px;
+ // background: var(--grey-light);
+ // box-shadow: 0 0 0 1px var(--color-theme);
+
+ // &:hover {
+ // transform: unset;
+ // box-shadow: 0 0 0 1px var(--color-primary);
+ // filter: brightness(0.95);
+ // }
+ // }
+
+ // &-circle-icon {
+ // align-content: center;
+ // width: 40px;
+ // height: 40px;
+ // border-radius: 100%;
+ // background: var(--white);
+ // box-shadow: var(--globals-button-shadow);
+ // }
+
+ // &-card-active {
+ // align-items: center;
+
+ // .icon {
+ // &--variant-button {
+ // margin-right: 5px;
+ // padding: 5px;
+ // border-radius: 20px;
+
+ // img {
+ // filter: invert(1);
+ // width: 22px !important;
+ // height: 22px !important;
+ // }
+ // }
+ // }
+ // }
+ }
+
+ // &--active {
+ // background: var(--color-primary) !important;
+ // }
+
+ // &--expand {
+ // width: 100%;
+ // }
+
+ // &--center {
+ // margin: auto;
+ // }
+
+ // &--navbar {
+ // padding: 13px 32px !important;
+ // white-space: nowrap;
+ // }
+}
diff --git a/src/client/packages/ui/src/components/Button/Button.tsx b/src/client/packages/ui/src/components/Button/Button.tsx
new file mode 100644
index 0000000..56efc33
--- /dev/null
+++ b/src/client/packages/ui/src/components/Button/Button.tsx
@@ -0,0 +1,110 @@
+"use client";
+
+import type { IconType } from "@repo/types/IconType";
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import Link from "next/link";
+import { type FC, type PropsWithChildren, useState } from "react";
+import { ClipLoader } from "react-spinners";
+
+import { Icon, type IconProps } from "../Icon/Icon";
+import styles from "./Button.module.scss";
+
+export type ButtonProps = {
+ variant?: "default" | "outline" | "card" | "red";
+ disabled?: boolean;
+ expand?: boolean;
+ grow?: boolean;
+ growMobile?: boolean;
+ center?: boolean;
+ active?: boolean;
+ icon?: IconType;
+ iconBackground?: IconProps["background"];
+ iconInvert?: boolean;
+ navbar?: boolean;
+ blank?: boolean;
+ uppercased?: boolean;
+ onClick?: string | (() => Promise) | (() => void);
+ afterClick?: () => void;
+ redirect?: boolean;
+ slim?: boolean;
+};
+
+// TODO: Rewrite this with CSS variables?
+const getIconVariant = (variant: ButtonProps["variant"]) => {
+ switch (variant) {
+ case "card":
+ return "card";
+ default:
+ return "button";
+ }
+};
+
+const bem = bemClassNames(styles, "button");
+
+export const Button: FC> = ({
+ variant = "default",
+ disabled,
+ expand,
+ grow,
+ growMobile,
+ center,
+ active,
+ icon,
+ iconBackground,
+ iconInvert,
+ navbar,
+ blank,
+ uppercased = false,
+ onClick,
+ afterClick,
+ redirect,
+ slim,
+ children,
+}) => {
+ const Component = typeof onClick === "string" ? (redirect ? "a" : Link) : "button";
+
+ const href = onClick instanceof Function ? "" : (onClick ?? "");
+ const onClickHandler = onClick instanceof Function ? onClick : undefined;
+
+ const [loading, setLoading] = useState(false);
+
+ return (
+ {
+ afterClick?.();
+ if (loading || !onClickHandler) {
+ return;
+ }
+ setLoading(true);
+ setLoading((await onClickHandler()) ?? false);
+ }}
+ className={bem({
+ variant,
+ expand,
+ grow,
+ center,
+ active,
+ navbar,
+ uppercased,
+ growMobile,
+ slim,
+ })}
+ {...{ disabled }}
+ >
+ {icon && (
+
+ )}
+ {loading && }
+ {children}
+
+ );
+};
diff --git a/src/client/packages/ui/src/components/Flex/Flex.module.scss b/src/client/packages/ui/src/components/Flex/Flex.module.scss
new file mode 100644
index 0000000..368e036
--- /dev/null
+++ b/src/client/packages/ui/src/components/Flex/Flex.module.scss
@@ -0,0 +1,52 @@
+@use "../definition" as d;
+@use "../mixins" as m;
+@use "../variables" as v;
+
+.flex {
+ display: flex;
+ position: relative;
+ box-sizing: border-box;
+ max-width: 100%;
+
+ &--fill {
+ width: 100%;
+ height: 100%;
+ }
+
+ &--variant {
+ &-popup,
+ &-card {
+ @include m.card;
+ }
+ }
+
+ &--background {
+ &-always-dark {
+ background: var(--color-mono-always-dark);
+
+ --color-mono-light: #{v.$mono-light};
+ }
+
+ &-dark {
+ background: var(--color-mono-dark);
+ }
+
+ &-light {
+ background: var(--color-mono-light);
+ }
+
+ &-white {
+ background: var(--color-mono-white);
+ }
+ }
+
+ @each $key, $value in d.$predefined-block {
+ &--#{$key} {
+ @each $item, $item-value in $value {
+ &-#{$item} {
+ #{$key}: var(--#{$key}-#{$item});
+ }
+ }
+ }
+ }
+}
diff --git a/src/client/packages/ui/src/components/Flex/Flex.tsx b/src/client/packages/ui/src/components/Flex/Flex.tsx
new file mode 100644
index 0000000..595d7de
--- /dev/null
+++ b/src/client/packages/ui/src/components/Flex/Flex.tsx
@@ -0,0 +1,79 @@
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import type { Property } from "csstype";
+import type { FC, PropsWithChildren } from "react";
+
+import styles from "./Flex.module.scss";
+
+const bem = bemClassNames(styles);
+
+export type FlexProps = PropsWithChildren<{
+ justifyContent?: Property.JustifyContent;
+ alignContent?: Property.AlignContent;
+ alignItems?: Property.AlignItems;
+ alignSelf?: Property.AlignSelf;
+ direction?: Property.FlexDirection;
+ wrap?: Property.FlexWrap;
+ gap?: Property.Gap;
+ basis?: Property.FlexBasis;
+ height?: Property.Height;
+ maxHeight?: Property.MaxHeight;
+ width?: Property.Width;
+ maxWidth?: Property.MaxWidth;
+ grow?: Property.FlexGrow | boolean;
+ fill?: boolean;
+ padding?: "content" | "card-content" | "block" | "block-small" | "groupbox" | "half-rem";
+ margin?: "vertical-large" | "vertical-medium" | "vertical-rem" | "horizontal-half-rem";
+ className?: string;
+ variant?: "card" | "popup";
+ background?: "always-dark" | "dark" | "light" | "white";
+}>;
+
+export const Flex: FC = ({
+ justifyContent,
+ alignContent,
+ alignItems,
+ alignSelf,
+ direction,
+ wrap,
+ gap,
+ basis,
+ height,
+ maxHeight,
+ width,
+ maxWidth,
+ grow,
+ fill,
+ padding,
+ margin,
+ className,
+ variant,
+ background,
+ children,
+ // onClick,
+ // onWheel,
+ // onMouseDown,
+ // onMouseUp,
+ // onMouseEnter,
+ // onMouseLeave,
+}) => (
+
+ {children}
+
+);
diff --git a/src/client/packages/ui/src/components/Icon/Icon.module.scss b/src/client/packages/ui/src/components/Icon/Icon.module.scss
new file mode 100644
index 0000000..a56c06b
--- /dev/null
+++ b/src/client/packages/ui/src/components/Icon/Icon.module.scss
@@ -0,0 +1,102 @@
+.icon {
+ display: inline-block;
+ transform: var(--icon-transform);
+ transition: var(--transition-default);
+ opacity: var(--icon-opacity);
+
+ $this: &;
+
+ &__image {
+ display: block;
+ align-self: center;
+ width: 24px;
+ height: 24px;
+ filter: var(--icon-filter);
+ }
+
+ &--invert {
+ --icon-filter: invert(1);
+
+ &-auto {
+ --icon-filter: var(--themed-filter);
+ }
+ }
+
+ &--active {
+ --icon-filter: var(--primary-color-filter) !important;
+ }
+
+ &--disabled {
+ transform: none;
+ cursor: not-allowed !important;
+ filter: none;
+ }
+
+ &--clickable {
+ cursor: pointer;
+
+ &:hover {
+ --icon-transform: scale(1.11);
+ --icon-opacity: 0.9;
+ }
+
+ &:active {
+ --icon-filter: brightness(0.9);
+ --icon-transition: var(--transition-click);
+ }
+ }
+
+ &--variant {
+ &-circle {
+ display: block;
+ padding: .6rem;
+ border-radius: 100%;
+ background: var(--color-mono-light);
+ }
+
+ &-small-circle {
+ --icon-filter: invert(1);
+
+ display: block;
+ padding: 5px;
+ border-radius: 100%;
+ background: var(--color-primary);
+ }
+
+ &-primary {
+ --icon-filter: invert(1) var(--primary-color-filter);
+ }
+
+ &-card {
+ padding: 5px;
+ border-radius: 20px;
+
+ #{$this}__image {
+ width: 22px !important;
+ height: 22px !important;
+ }
+ }
+ }
+
+ &--background {
+ &-primary {
+ background: var(--color-primary);
+ }
+
+ &-green {
+ background: var(--color-green);
+ }
+
+ &-red {
+ background: var(--color-red);
+ }
+
+ &-purple {
+ background: var(--color-purple);
+ }
+
+ &-silver {
+ background: var(--color-silver);
+ }
+ }
+}
diff --git a/src/client/packages/ui/src/components/Icon/Icon.tsx b/src/client/packages/ui/src/components/Icon/Icon.tsx
new file mode 100644
index 0000000..0c55f25
--- /dev/null
+++ b/src/client/packages/ui/src/components/Icon/Icon.tsx
@@ -0,0 +1,74 @@
+"use client";
+import type { IconType } from "@repo/types/IconType";
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import Image from "next/image";
+import type { EventHandler, FC, KeyboardEvent, MouseEvent } from "react";
+
+import styles from "./Icon.module.scss";
+
+export const getIcon = (icon: string) => {
+ if (CustomIcons.includes(icon)) {
+ return `/icons/custom/${icon}.png`;
+ }
+ return `/icons/${icon}.svg`;
+};
+
+export const CustomIcons = ["battery", "http", "inverter", "serial"];
+
+export type IconProps = {
+ icon: IconType;
+ variant?: "circle" | "small-circle" | "primary" | "card" | "button";
+ background?: "primary" | "green" | "red" | "purple" | "silver";
+ size?: number;
+ invert?: boolean | "auto";
+ title?: string;
+ active?: boolean;
+ disabled?: boolean;
+ clickable?: boolean;
+ onClick?: EventHandler;
+};
+
+const bem = bemClassNames(styles);
+
+export const Icon: FC = ({
+ icon,
+ size = 24,
+ variant,
+ background,
+ invert = false,
+ title,
+ active,
+ disabled,
+ clickable,
+ onClick,
+}) => {
+ return (
+ // biome-ignore lint/a11y/noStaticElementInteractions: off
+ // biome-ignore lint/a11y/useKeyWithClickEvents: off
+
+ e.key === "Enter" && onClick && onClick(e)}
+ tabIndex={onClick ? 0 : undefined}
+ style={{ width: size, height: size }}
+ loading="eager"
+ unoptimized
+ />
+
+ );
+};
diff --git a/src/client/packages/ui/src/components/Input/Input.module.scss b/src/client/packages/ui/src/components/Input/Input.module.scss
new file mode 100644
index 0000000..b80b367
--- /dev/null
+++ b/src/client/packages/ui/src/components/Input/Input.module.scss
@@ -0,0 +1,158 @@
+@use "sass:map";
+@use "sass:color";
+@use "../definition" as d;
+
+.input {
+ $this: &;
+
+ display: flex;
+ position: relative;
+ flex-direction: column;
+ justify-content: space-between;
+ min-width: 200px;
+ gap: 5px;
+
+ --icon-filter: var(--themed-filter);
+
+ &--grow {
+ flex-grow: 1;
+
+ &Mobile {
+ @media (width <= 768px) {
+ flex-grow: 1;
+ }
+ }
+ }
+
+ &__wrapper {
+ display: flex;
+ position: relative;
+ flex-grow: 1;
+ align-items: center;
+ transition: var(--transition-default);
+ border-radius: var(--border-radius-small);
+ background: var(--color-primary-transparent);
+ cursor: text;
+ gap: 12px;
+
+ --icon-opacity: .5;
+
+ &:focus-within {
+ background: unset;
+ box-shadow: 0 0 0 3px var(--color-primary) inset;
+
+ --icon-opacity: .8;
+ }
+
+ &:has(input:invalid) {
+ background: rgba(map.get(map.get(d.$predefined-text, "color"), "red"), 0.2);
+ box-shadow: 0 0 0 3px var(--color-red) inset;
+
+ --color-primary: var(--color-red);
+
+ &:focus-within {
+ background: var(--primary-background);
+ box-shadow: 0 0 0 3px var(--color-red) inset;
+ }
+ }
+
+ &--controls {
+ padding: 10px 12px;
+
+ --icon-filter: none;
+
+ #{$this}__input {
+ height: unset;
+ padding: 0;
+ }
+ }
+ }
+
+ &__input {
+ box-sizing: border-box;
+ flex-grow: 1;
+ width: 100%;
+ height: 46px;
+ padding: 12px 15px;
+ border: none;
+ outline: transparent;
+ background: transparent;
+ color: var(--color-mono-dark);
+ font-family: inherit;
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-medium);
+
+ &[type="pick"] {
+ appearance: none;
+ cursor: pointer;
+ }
+ }
+
+ &__right-control,
+ &__left-control {
+ transition: var(--transition-default);
+ }
+
+ &__icon {
+ position: absolute;
+ right: 1.2rem;
+ pointer-events: none;
+ user-select: none;
+ }
+
+ &--hasIcon {
+ #{$this}__input {
+ padding-right: 50px;
+ }
+ }
+
+ &--type {
+ &-checkbox, &-radio {
+ min-width: unset;
+
+ #{$this}__input {
+ display: flex;
+ flex-grow: 0;
+ flex-shrink: 0;
+ align-items: center;
+ width: clamp(25px, 2.1vw, 30px);
+ height: clamp(25px, 2.1vw, 30px);
+ margin: 0;
+ padding: 10px;
+ transition: var(--transition-default);
+ border-radius: 100%;
+ background: color-mix(in srgb, var(--color-silver), #fff0 70%);
+ cursor: pointer;
+ appearance: none;
+
+ &::after {
+ content: "";
+ display: block;
+ position: relative;
+ top: -1px;
+ width: 3px;
+ height: 10px;
+ margin: 0 auto;
+ transform: rotate(45deg);
+ transition: border 0.1s ease-in-out;
+ }
+
+ &:checked {
+ background: var(--color-primary);
+
+ &::after {
+ border: solid 3px var(--color-mono-light);
+ border-top: unset;
+ border-left: unset;
+ }
+ }
+ }
+
+ #{$this}__wrapper {
+ padding: 10px 12px;
+ padding-right: 20px;
+ cursor: pointer;
+ }
+ }
+ }
+}
diff --git a/src/client/packages/ui/src/components/Input/Input.test.tsx b/src/client/packages/ui/src/components/Input/Input.test.tsx
new file mode 100644
index 0000000..ea7a27e
--- /dev/null
+++ b/src/client/packages/ui/src/components/Input/Input.test.tsx
@@ -0,0 +1,222 @@
+/* eslint-disable @typescript-eslint/no-unused-expressions */
+import "@testing-library/jest-dom";
+
+import { render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { Provider } from "react-redux";
+import configureMockStore from "redux-mock-store";
+
+import { Input } from "./Input";
+
+const mockStore = configureMockStore();
+const store = mockStore({ settings: { localeState: "cs-CZ" } });
+const makeStoreWithLocale = (locale: string) => mockStore({ settings: { localeState: locale } });
+
+describe("Input behavior", () => {
+ const onValueChange = jest.fn();
+ const onNumberChange = jest.fn();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ it("Normal", async () => {
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole("textbox");
+
+ // Test that the input is empty by default
+ expect(input).toHaveValue("");
+ expect(onValueChange).toHaveBeenCalledTimes(0);
+ expect(onNumberChange).toHaveBeenCalledTimes(0);
+
+ // Simulate user typing
+ await userEvent.type(input, "1234");
+
+ // Test that the input has the correct value and is not formatted
+ expect(input).toHaveValue("1234");
+ expect(onValueChange).toHaveBeenCalledTimes(4);
+ expect(onNumberChange).toHaveBeenCalledTimes(0);
+ });
+
+ it("Default value", async () => {
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole("textbox");
+
+ // Test that the input has the correct default value and is not formatted
+ expect(input).toHaveValue("1234");
+ expect(onValueChange).toHaveBeenCalledTimes(0);
+ expect(onNumberChange).toHaveBeenCalledTimes(0);
+ });
+
+ it("Number", async () => {
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole("textbox");
+
+ // Simulate user typing
+ await userEvent.type(input, "234");
+
+ // Test that the input has the correct value and is formatted
+ expect(input).toHaveValue("1 234");
+ expect(onValueChange).toHaveBeenCalledTimes(3);
+ expect(onNumberChange).toHaveBeenCalledTimes(3);
+
+ // Test that control buttons are not rendered by default
+ const [down, up] = screen.queryAllByRole("img");
+ expect(down).toBeUndefined();
+ expect(up).toBeUndefined();
+ });
+
+ it("Number with controls", async () => {
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole("textbox");
+ const [down, up] = screen.getAllByRole("img");
+
+ // Simulate user clicking on the control button
+ up && (await userEvent.click(up));
+ // Test that the input has the correct value and is formatted
+ expect(input).toHaveValue("1 001");
+
+ // Simulate user clicking on the control button
+ down && (await userEvent.click(down));
+ // Test that the input has the correct value and is formatted
+ expect(input).toHaveValue("1 000");
+ expect(onValueChange).toHaveBeenCalledTimes(2);
+ expect(onNumberChange).toHaveBeenCalledTimes(2);
+ });
+
+ it("Number with min and max", async () => {
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole("textbox");
+ const [down, up] = screen.getAllByRole("img");
+
+ await userEvent.type(input, "1234");
+
+ expect(input).toHaveValue("10");
+ // TODO: Optimize calls?
+ expect(onValueChange).toHaveBeenCalledTimes(4);
+ expect(onNumberChange).toHaveBeenCalledTimes(4);
+
+ // Simulate user clicking on the control button
+ up && (await userEvent.click(up));
+ // Test that the input value is not changed
+ expect(input).toHaveValue("10");
+ // TODO: Optimize calls?
+ expect(onValueChange).toHaveBeenCalledTimes(5);
+ expect(onNumberChange).toHaveBeenCalledTimes(5);
+ // Simulate user clicking on the control button
+ down && (await userEvent.click(down));
+ // Test that the input value decreased by the step
+ expect(input).toHaveValue("9");
+ expect(onValueChange).toHaveBeenCalledTimes(6);
+ expect(onNumberChange).toHaveBeenCalledTimes(6);
+ });
+
+ it("Number with step", async () => {
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole("textbox");
+ const [down, up] = screen.getAllByRole("img");
+
+ expect(input).toHaveValue("50");
+ expect(onValueChange).toHaveBeenCalledTimes(0);
+ expect(onNumberChange).toHaveBeenCalledTimes(0);
+ // Simulate user clicking on the control button
+ up && (await userEvent.click(up));
+ // Test that the input value increased by the step
+ expect(input).toHaveValue("60");
+ expect(onValueChange).toHaveBeenCalledTimes(1);
+ expect(onNumberChange).toHaveBeenCalledTimes(1);
+ // Simulate user clicking on the control button
+ down && (await userEvent.click(down));
+ // Test that the input value decreased by the step
+ expect(input).toHaveValue("50");
+ expect(onValueChange).toHaveBeenCalledTimes(2);
+ expect(onNumberChange).toHaveBeenCalledTimes(2);
+ });
+
+ it("Number with postponed change and en-US locale", async () => {
+ render(
+
+
+ ,
+ );
+
+ const input = screen.getByRole("textbox");
+
+ // Simulate user typing
+ await userEvent.type(input, "234");
+
+ // Test that the input has the correct value and is formatted
+ expect(input).toHaveValue("1,234");
+ expect(onValueChange).toHaveBeenCalledTimes(0);
+ expect(onNumberChange).toHaveBeenCalledTimes(0);
+
+ // Simulate user clicking outside the input
+ await userEvent.click(document.body);
+
+ // Test that the input has the correct value and is formatted
+ expect(input).toHaveValue("1,234");
+ expect(onValueChange).toHaveBeenCalledTimes(1);
+ expect(onNumberChange).toHaveBeenCalledTimes(1);
+
+ // Simulate user typing
+ await userEvent.type(input, "567");
+
+ // Test that the input has the correct value and is formatted
+ expect(input).toHaveValue("1,234,567");
+ expect(onValueChange).toHaveBeenCalledTimes(1);
+ expect(onNumberChange).toHaveBeenCalledTimes(1);
+
+ // Simulate user pressing Enter
+ await userEvent.type(input, "{enter}");
+
+ // Test that the input has the correct value and is formatted
+ expect(input).toHaveValue("1,234,567");
+ expect(onValueChange).toHaveBeenCalledTimes(2);
+ expect(onNumberChange).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/src/client/packages/ui/src/components/Input/Input.tsx b/src/client/packages/ui/src/components/Input/Input.tsx
new file mode 100644
index 0000000..47cf063
--- /dev/null
+++ b/src/client/packages/ui/src/components/Input/Input.tsx
@@ -0,0 +1,291 @@
+"use client";
+
+import { type LOCALES, selectLocaleState } from "@repo/store/slices/settingsSlice";
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import type { Property } from "csstype";
+import { type FC, type HTMLInputTypeAttribute, useCallback, useEffect, useState } from "react";
+import { useSelector } from "react-redux";
+
+import { openSans } from "../../fonts";
+import { Icon, type IconProps } from "../Icon/Icon";
+import { Text } from "../Text/Text";
+import styles from "./Input.module.scss";
+
+const bem = bemClassNames(styles);
+
+export type InputProps = {
+ type?: HTMLInputTypeAttribute | "pick" | "long-text";
+ value?: string | number;
+ title?: string;
+ placeholder?: string;
+ icon?: IconProps["icon"];
+ unit?: string;
+ required?: boolean;
+ invalid?: boolean | string;
+ min?: number;
+ max?: number;
+ step?: number;
+ controls?: boolean;
+ basis?: Property.FlexBasis;
+ grow?: boolean;
+ growMobile?: boolean;
+ postponedChanged?: boolean;
+ options?: { name: string; value: string }[];
+ focus?: boolean;
+ checked?: boolean;
+
+ onValueChange?(value: string): void;
+ onNumberChange?(value: number): void;
+ onCheckboxChange?(value: boolean): void;
+};
+
+export const Input: FC = ({
+ type,
+ value,
+ title,
+ placeholder,
+ icon,
+ unit,
+ required,
+ invalid,
+ min,
+ max,
+ step = 1,
+ controls,
+ basis,
+ grow,
+ growMobile,
+ postponedChanged,
+ options,
+ focus,
+ checked,
+ onValueChange,
+ onNumberChange,
+ onCheckboxChange,
+}) => {
+ if (type === "pick" && !icon) {
+ icon = "chevron-down";
+ }
+
+ const offsetValue = (offset: number) => {
+ return processValue(
+ parseLocalizedFloat(valueState !== "" ? valueState : "0", currentLocale) + offset,
+ );
+ };
+
+ const processValue = useCallback(
+ (value: number) => {
+ if (min !== undefined && value < min) {
+ return min;
+ }
+ if (max && value > max) {
+ return max;
+ }
+ if (Number.isNaN(value)) {
+ return min ?? 0;
+ }
+
+ return value;
+ },
+ [max, min],
+ );
+
+ const parseLocalizedFloat = useCallback((input: string, locale: LOCALES) => {
+ const formatter = new Intl.NumberFormat(locale);
+ const parts = formatter.formatToParts(1234.5); // Example number to extract separators
+
+ const decimalSeparator = parts.find((part) => part.type === "decimal")?.value || ".";
+ const groupSeparator = parts.find((part) => part.type === "group")?.value || ",";
+
+ // Replace group separator with nothing and decimal separator with '.'
+ const normalizedInput = input.split(groupSeparator).join("").split(decimalSeparator).join(".");
+
+ // Parse the normalized number
+ return Number.parseFloat(normalizedInput);
+ }, []);
+
+ const formatLocalizedFloat = useCallback((value: number, locale: LOCALES) => {
+ return new Intl.NumberFormat(locale).format(value);
+ }, []);
+
+ const commitNumberValue = (value: number, triggerChangeEvent = false) => {
+ const formattedValue = formatLocalizedFloat(value, currentLocale);
+ setValueState(formattedValue);
+ if (triggerChangeEvent) {
+ onNumberChange?.(value);
+ onValueChange?.(formattedValue);
+ }
+ };
+
+ const commitEmptyValue = (triggerChangeEvent = false) => {
+ setValueState("");
+ if (triggerChangeEvent) {
+ onValueChange?.("");
+ }
+ };
+
+ const [valueState, setValueState] = useState(value?.toString() ?? "");
+
+ const currentLocale = useSelector(selectLocaleState);
+
+ useEffect(() => {
+ let newValue: number;
+ if (typeof value === "number") {
+ newValue = value;
+ } else {
+ newValue = processValue(parseLocalizedFloat(value ?? "", currentLocale));
+ }
+ if (type === "number") {
+ setValueState(formatLocalizedFloat(newValue, currentLocale));
+ } else {
+ setValueState(value?.toString() ?? "");
+ }
+ }, [currentLocale, processValue, type, value, formatLocalizedFloat, parseLocalizedFloat]);
+
+ const Component = type === "long-text" ? "textarea" : type === "pick" ? "select" : "input";
+
+ return (
+ // biome-ignore lint/a11y/noLabelWithoutControl: off
+
+ {title && {title} }
+
+ {controls && type === "number" && (
+ // biome-ignore lint/a11y/noStaticElementInteractions: off
+ // biome-ignore lint/a11y/useKeyWithClickEvents: off
+
e.preventDefault()}>
+ {
+ e.preventDefault();
+ commitNumberValue(offsetValue(-step), true);
+ }}
+ size={16}
+ />
+
+ )}
+
{
+ if (type !== "number") {
+ if (type === "checkbox" && e.target instanceof HTMLInputElement) {
+ onCheckboxChange?.(e.target.checked);
+ return;
+ }
+
+ if (type !== "radio") {
+ setValueState(e.target.value);
+ }
+
+ if (!postponedChanged) {
+ onValueChange?.(e.target.value);
+ }
+ return;
+ }
+
+ const parsedValue = parseLocalizedFloat(e.target.value, currentLocale);
+ if (Number.isNaN(parsedValue)) {
+ commitEmptyValue(!postponedChanged);
+ return;
+ }
+
+ commitNumberValue(
+ postponedChanged ? parsedValue : processValue(parsedValue),
+ !postponedChanged,
+ );
+ }}
+ onBlur={(e) => {
+ if (!postponedChanged) {
+ return;
+ }
+
+ if (type !== "number") {
+ onValueChange?.(e.target.value);
+ return;
+ }
+
+ const parsedValue = parseLocalizedFloat(e.target.value, currentLocale);
+ if (Number.isNaN(parsedValue)) {
+ commitEmptyValue(true);
+ return;
+ }
+
+ commitNumberValue(processValue(parsedValue), true);
+ }}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") {
+ if (postponedChanged && e.target instanceof HTMLInputElement) {
+ if (type !== "number") {
+ onValueChange?.(e.target.value);
+ } else {
+ const parsedValue = parseLocalizedFloat(e.target.value, currentLocale);
+ if (Number.isNaN(parsedValue)) {
+ commitEmptyValue(true);
+ return;
+ }
+
+ commitNumberValue(processValue(parsedValue), true);
+ }
+ }
+ }
+ }}
+ >
+ {options?.map((option) => (
+
+ {option.name}
+
+ ))}
+
+ {unit &&
{unit} }
+ {icon && (
+
+
+
+ )}
+ {controls && type === "number" && (
+
+ = max
+ ? "silver"
+ : "primary"
+ }
+ disabled={
+ max !== undefined && parseLocalizedFloat(valueState, currentLocale) >= max
+ }
+ onClick={(e) => {
+ e.preventDefault();
+ commitNumberValue(offsetValue(step), true);
+ }}
+ size={16}
+ />
+
+ )}
+ {placeholder && (type === "checkbox" || type === "radio") && (
+
+ {placeholder}
+
+ )}
+
+
+ );
+};
diff --git a/src/client/packages/ui/src/components/Navbar/Navbar.module.scss b/src/client/packages/ui/src/components/Navbar/Navbar.module.scss
new file mode 100644
index 0000000..4dc2520
--- /dev/null
+++ b/src/client/packages/ui/src/components/Navbar/Navbar.module.scss
@@ -0,0 +1,22 @@
+.navbar {
+ box-sizing: border-box;
+ flex-direction: column;
+ align-items: center;
+ min-width: 300px;
+ background: #fff;
+ gap: 5px;
+
+ @media (width <= 1000px) {
+ display: none;
+ position: fixed;
+ z-index: 100;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+
+ &--visible {
+ display: flex;
+ }
+}
diff --git a/src/client/packages/ui/src/components/Navbar/Navbar.tsx b/src/client/packages/ui/src/components/Navbar/Navbar.tsx
new file mode 100644
index 0000000..f0c9b06
--- /dev/null
+++ b/src/client/packages/ui/src/components/Navbar/Navbar.tsx
@@ -0,0 +1,16 @@
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import type { FC, PropsWithChildren } from "react";
+
+import { Flex } from "../Flex/Flex";
+import styles from "./Navbar.module.scss";
+
+const bem = bemClassNames(styles);
+
+export type NavbarProps = {
+ width?: string;
+ visible?: boolean;
+};
+
+export const Navbar: FC> = ({ visible = false, children }) => {
+ return {children} ;
+};
diff --git a/src/client/packages/ui/src/components/Navbar/components/NavbarItem/NavbarItem.module.scss b/src/client/packages/ui/src/components/Navbar/components/NavbarItem/NavbarItem.module.scss
new file mode 100644
index 0000000..528056f
--- /dev/null
+++ b/src/client/packages/ui/src/components/Navbar/components/NavbarItem/NavbarItem.module.scss
@@ -0,0 +1,22 @@
+.navbar-item {
+ display: flex;
+ box-sizing: border-box;
+ align-items: center;
+ width: 80%;
+ padding: 13px 15px;
+ border-radius: 10px;
+ background: transparent;
+ color: inherit;
+ text-decoration: none;
+ gap: 15px;
+
+ &--active {
+ background: var(--color-primary) !important;
+ box-shadow: var(--box-shadow-card);
+ color: var(--color-mono-light);
+ }
+
+ &:hover {
+ background: var(--color-mono-light);
+ }
+}
diff --git a/src/client/packages/ui/src/components/Navbar/components/NavbarItem/NavbarItem.tsx b/src/client/packages/ui/src/components/Navbar/components/NavbarItem/NavbarItem.tsx
new file mode 100644
index 0000000..a3955f0
--- /dev/null
+++ b/src/client/packages/ui/src/components/Navbar/components/NavbarItem/NavbarItem.tsx
@@ -0,0 +1,30 @@
+import { Icon, type IconProps } from "@repo/ui/Icon";
+import { Text } from "@repo/ui/Text";
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import { useActivePathname } from "@repo/utils/useActivePathname";
+import Link from "next/link";
+import type { FC } from "react";
+
+import styles from "./NavbarItem.module.scss";
+
+const bem = bemClassNames(styles);
+
+type NavbarItemProps = {
+ href: string;
+ name: string;
+ icon: IconProps["icon"];
+ onClick?: () => void;
+};
+
+export const NavbarItem: FC = ({ href, name, icon, onClick }) => {
+ const { active } = useActivePathname(href);
+
+ return (
+
+
+
+ {name}
+
+
+ );
+};
diff --git a/src/client/packages/ui/src/components/NavbarLayout/NavbarLayout.module.scss b/src/client/packages/ui/src/components/NavbarLayout/NavbarLayout.module.scss
new file mode 100644
index 0000000..0c0caf7
--- /dev/null
+++ b/src/client/packages/ui/src/components/NavbarLayout/NavbarLayout.module.scss
@@ -0,0 +1,27 @@
+.navbar-layout {
+ display: flex;
+ width: 100%;
+
+ &__content {
+ box-sizing: border-box;
+ flex-grow: 1;
+ width: 0;
+ padding: 2rem;
+
+ @media (width <= 1000px) {
+ width: 100%;
+ padding: 1rem;
+ }
+ }
+
+ &__mobile-menu-button {
+ display: none;
+ position: absolute;
+ top: .8rem;
+ right: 1rem;
+
+ @media (width <= 1000px) {
+ display: block;
+ }
+ }
+}
diff --git a/src/client/packages/ui/src/components/NavbarLayout/NavbarLayout.tsx b/src/client/packages/ui/src/components/NavbarLayout/NavbarLayout.tsx
new file mode 100644
index 0000000..905c04a
--- /dev/null
+++ b/src/client/packages/ui/src/components/NavbarLayout/NavbarLayout.tsx
@@ -0,0 +1,65 @@
+"use client";
+
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import { useTranslation } from "@repo/utils/useTranslation";
+import Image from "next/image";
+import { type FC, type PropsWithChildren, useMemo, useState } from "react";
+
+import { Column } from "../../partials/FlexPartials/FlexPartials";
+import { Flex } from "../Flex/Flex";
+import { Icon, type IconProps } from "../Icon/Icon";
+import { NavbarItem } from "../Navbar/components/NavbarItem/NavbarItem";
+import { Navbar } from "../Navbar/Navbar";
+import styles from "./NavbarLayout.module.scss";
+
+export type NavbarLayoutProps = PropsWithChildren;
+
+const bem = bemClassNames(styles);
+
+export const NavbarLayout: FC = ({ children }) => {
+ const { t } = useTranslation();
+ const [showNavbar, setShowNavbar] = useState(false);
+
+ const routes: { url: string; name: string; icon: IconProps["icon"] }[] = useMemo(
+ () => [
+ { url: "/", name: t("Overview"), icon: "grid-mixed" },
+ { url: "/inspector", name: t("Inspector"), icon: "chart-square" },
+ { url: "/handlers", name: t("Handlers"), icon: "processor" },
+ { url: "/actions", name: t("Actions"), icon: "branch-horizontal" },
+ ],
+ [t],
+ );
+
+ const killMenu = () => {
+ setShowNavbar(false);
+ };
+
+ return (
+
+
+
+ {routes.map((route) => (
+
+ ))}
+
+
+ {children}
+
+
+ setShowNavbar(!showNavbar)} icon={"menu"} size={30} />
+
+
+ );
+};
diff --git a/src/client/packages/ui/src/components/Popup/Popup.module.scss b/src/client/packages/ui/src/components/Popup/Popup.module.scss
new file mode 100644
index 0000000..841fedc
--- /dev/null
+++ b/src/client/packages/ui/src/components/Popup/Popup.module.scss
@@ -0,0 +1,31 @@
+.popup {
+ display: none;
+ position: fixed;
+ z-index: 500;
+ top: 0;
+ left: 0;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ height: 100dvh;
+
+ --icon-filter: var(--themed-filter);
+
+ &--visible {
+ display: flex;
+ }
+
+ &__header {
+ padding-bottom: .5rem;
+ }
+
+ &__overlay {
+ position: absolute;
+ right: 0;
+ left: 0;
+ width: 100%;
+ height: 100dvh;
+ background: rgba(0 0 0 / 70%);
+ backdrop-filter: blur(17px);
+ }
+}
diff --git a/src/client/packages/ui/src/components/Popup/Popup.tsx b/src/client/packages/ui/src/components/Popup/Popup.tsx
new file mode 100644
index 0000000..87e0fdc
--- /dev/null
+++ b/src/client/packages/ui/src/components/Popup/Popup.tsx
@@ -0,0 +1,98 @@
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import Link from "next/link";
+import { type FC, type PropsWithChildren, useEffect } from "react";
+
+import { Column } from "../../partials/FlexPartials/FlexPartials";
+import { Flex } from "../Flex/Flex";
+import { Icon } from "../Icon/Icon";
+import { Text } from "../Text/Text";
+import styles from "./Popup.module.scss";
+
+const bem = bemClassNames(styles);
+
+export type PopupProps = {
+ visible?: boolean;
+ title?: string;
+ titleHref?: string;
+
+ titleOnClick?(): void;
+ onOpen?(): void;
+ onClose?(): void;
+ onEnter?(): void;
+};
+
+export const Popup: FC> = ({
+ visible,
+ title,
+ titleHref,
+ titleOnClick,
+ onOpen,
+ onClose,
+ onEnter,
+ children,
+}) => {
+ useEffect(() => {
+ if (visible) {
+ onOpen?.();
+ }
+ }, [onOpen, visible]);
+
+ useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Escape") {
+ onClose?.();
+ }
+ };
+
+ const handleEnter = (event: KeyboardEvent) => {
+ if (event.key === "Enter") {
+ onEnter?.();
+ }
+ };
+
+ document.addEventListener("keydown", handleKeyDown);
+ document.addEventListener("keydown", handleEnter);
+ return () => {
+ document.removeEventListener("keydown", handleKeyDown);
+ document.removeEventListener("keydown", handleEnter);
+ };
+ });
+
+ return (
+ visible && (
+
+ {/** biome-ignore lint/a11y/noStaticElementInteractions: off */}
+ {/** biome-ignore lint/a11y/useKeyWithClickEvents: off */}
+
+
+
+
+
+
+ {title ? (
+ titleHref ? (
+
+
+ {title}
+
+
+ ) : (
+
+ {title}
+
+ )
+ ) : (
+
+ )}
+
+
+
+
+
+ {children}
+
+
+
+ )
+ );
+};
diff --git a/src/client/packages/ui/src/components/Separator/Separator.module.scss b/src/client/packages/ui/src/components/Separator/Separator.module.scss
new file mode 100644
index 0000000..23f58d2
--- /dev/null
+++ b/src/client/packages/ui/src/components/Separator/Separator.module.scss
@@ -0,0 +1,11 @@
+.separator {
+ content: "";
+
+ &--variant {
+ &-navbar {
+ width: 1px;
+ height: 27px;
+ background-color: var(--color-primary);
+ }
+ }
+}
diff --git a/src/client/packages/ui/src/components/Separator/Separator.tsx b/src/client/packages/ui/src/components/Separator/Separator.tsx
new file mode 100644
index 0000000..b65fffe
--- /dev/null
+++ b/src/client/packages/ui/src/components/Separator/Separator.tsx
@@ -0,0 +1,17 @@
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import type { Property } from "csstype";
+import type { FC } from "react";
+
+import styles from "./Separator.module.scss";
+
+const bem = bemClassNames(styles);
+
+export type SeparatorProps = {
+ width?: Property.Width;
+ height?: Property.Height;
+ variant?: "navbar";
+};
+
+export const Separator: FC = ({ variant, width, height }) => {
+ return
;
+};
diff --git a/src/client/packages/ui/src/components/Text/Text.module.scss b/src/client/packages/ui/src/components/Text/Text.module.scss
new file mode 100644
index 0000000..74eb32b
--- /dev/null
+++ b/src/client/packages/ui/src/components/Text/Text.module.scss
@@ -0,0 +1,102 @@
+@use "../definition" as d;
+@use "sass:math";
+
+.text {
+ font-size: var(--font-size-normal);
+
+ @each $key, $value in d.$predefined-text {
+ &--#{$key} {
+ @each $item, $item-value in $value {
+ &-#{$item} {
+ #{$key}: var(--#{$key}-#{$item});
+ }
+ }
+ }
+ }
+
+ &--uppercase {
+ text-transform: uppercase;
+ }
+
+ &--ellipsis {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ &--nowrap {
+ white-space: nowrap;
+ }
+
+ &--shadow {
+ text-shadow: 0 0 10px gray;
+ }
+
+ &--align {
+ &-center {
+ margin: auto;
+ text-align: center;
+ }
+
+ &-right {
+ text-align: right;
+ }
+
+ &-justify {
+ text-align: justify;
+ }
+ }
+
+ &--glitched {
+ position: relative;
+ overflow: hidden;
+
+ &::before {
+ content: attr(data-text);
+ position: absolute;
+ left: 1px;
+ max-width: 100%;
+ overflow: auto;
+ animation-name: glitch-animation-1;
+ animation-duration: 2s;
+ animation-timing-function: linear;
+ animation-delay: 0s;
+ animation-iteration-count: infinite;
+ text-shadow: -2px 0 red;
+ white-space: nowrap;
+ }
+
+ &::after {
+ content: attr(data-text);
+ position: absolute;
+ left: -1px;
+ max-width: 100%;
+ overflow: auto;
+ animation-name: glitch-animation-2;
+ animation-duration: 2s;
+ animation-timing-function: linear;
+ animation-delay: 0s;
+ animation-iteration-count: infinite;
+ text-shadow: -2px 0 blue;
+ white-space: nowrap;
+ }
+ }
+
+ @keyframes glitch-animation-1 {
+ $steps: 20;
+ @for $i from 0 through $steps {
+ #{math.percentage(calc($i*(1/$steps)))} {
+ clip: rect(math.random(150) + px, 100vw, math.random(150) + px, 0)
+ }
+ }
+ }
+
+ @keyframes glitch-animation-2 {
+ $steps: 20;
+ @for $i from 0 through $steps {
+ #{math.percentage(calc($i*(1/$steps)))} {
+ clip: rect(math.random(150) + px, 100vw, math.random(150) + px, 0)
+ }
+ }
+ }
+}
diff --git a/src/client/packages/ui/src/components/Text/Text.tsx b/src/client/packages/ui/src/components/Text/Text.tsx
new file mode 100644
index 0000000..5cb8de9
--- /dev/null
+++ b/src/client/packages/ui/src/components/Text/Text.tsx
@@ -0,0 +1,70 @@
+"use client";
+
+import { bemClassNames } from "@repo/utils/bemClassNames";
+import type { Property } from "csstype";
+import Link from "next/link";
+import type { FunctionComponent, PropsWithChildren } from "react";
+
+import { type Font, fontDefinitions } from "../../fonts";
+import styles from "./Text.module.scss";
+
+export type TextProps = {
+ className?: string;
+ uppercase?: boolean;
+ color?: "theme" | "mono-dark" | "mono-light" | "primary" | "secondary" | "silver" | "red";
+ weight?: "light" | "medium" | "bold" | "black";
+ lineHeight?: Property.LineHeight;
+ align?: Property.TextAlign;
+ ellipsis?: boolean;
+ nowrap?: boolean;
+ shadow?: boolean;
+ size?: "huge" | "large" | "medium" | "normal" | "small" | "tiny" | "logo";
+ font?: Font;
+ variant?: "logo";
+ href?: string;
+ glitched?: boolean;
+};
+
+const bem = bemClassNames(styles, "text");
+
+export const Text: FunctionComponent> = ({
+ className,
+ uppercase,
+ color,
+ weight,
+ lineHeight,
+ align,
+ ellipsis,
+ nowrap,
+ shadow,
+ size,
+ font = "open-sans",
+ variant,
+ href,
+ glitched,
+ children,
+}) => {
+ let resultClassName = bem({
+ uppercase,
+ color,
+ "font-weight": weight,
+ align,
+ ellipsis,
+ nowrap,
+ shadow,
+ "font-size": size,
+ variant,
+ glitched,
+ });
+ if (className) resultClassName += ` ${className}`;
+
+ if (font) {
+ resultClassName += ` ${fontDefinitions[font].className}`;
+ }
+
+ return (
+
+ {href ? {children} : children}
+
+ );
+};
diff --git a/src/client/packages/ui/src/components/common.scss b/src/client/packages/ui/src/components/common.scss
new file mode 100644
index 0000000..a8983bc
--- /dev/null
+++ b/src/client/packages/ui/src/components/common.scss
@@ -0,0 +1,50 @@
+@use "variables" as v;
+@use "definition" as d;
+
+html {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+body {
+ display: flex;
+ flex-direction: row;
+ flex-grow: 1;
+ margin: 0;
+ background: #f5f6fa;
+ color: var(--color-mono-dark);
+
+ a {
+ color: var(--color-primary);
+ text-decoration: none;
+ }
+}
+
+:root {
+ @each $key, $value in d.$predefined-text {
+ @each $sub-key, $sub-value in $value {
+ --#{$key}-#{$sub-key}: #{$sub-value};
+ }
+ }
+
+ @each $key, $value in d.$predefined-block {
+ @each $sub-key, $sub-value in $value {
+ --#{$key}-#{$sub-key}: #{$sub-value};
+ }
+ }
+
+ --icon-opacity: 0.9;
+ --body-background: linear-gradient(140deg, #fff7c703 0%, rgb(255 236 209 / 70%) 100%);
+ --default-button-icon-filter: invert(1);
+}
+
+html[data-theme='dark'] {
+ --body-background: #121619;
+ --default-button-icon-filter: unset;
+ --color-mono-always-dark: #282E32;
+ --color-mono-dark: #{v.$mono-white};
+ --color-mono-light: #{v.$mono-dark};
+ --color-mono-white: #1d2225;
+ --themed-filter: invert(1);
+}
diff --git a/src/client/packages/ui/src/components/definition.scss b/src/client/packages/ui/src/components/definition.scss
new file mode 100644
index 0000000..7e12937
--- /dev/null
+++ b/src/client/packages/ui/src/components/definition.scss
@@ -0,0 +1,67 @@
+@use "variables" as v;
+
+$predefined-text: (
+ color: (
+ "mono-always-dark": v.$mono-dark,
+ "mono-dark": v.$mono-dark,
+ "mono-light": v.$mono-light,
+ "mono-white": v.$mono-white,
+ "primary": v.$primary,
+ "primary-transparent": v.$primary-transparent,
+ "secondary": v.$secondary,
+ "silver": v.$silver,
+ "red": v.$red,
+ "orange": v.$orange,
+ "green": v.$green,
+ "theme": var(--color-primary),
+ ),
+ font-weight: (
+ "light": 300,
+ "normal": 400,
+ "medium": 600,
+ "bold": 700,
+ "black": 900,
+ ),
+ font-size: (
+ "tiny": clamp(12px, 1.2vw, 14px),
+ "small": clamp(15px, 1.4vw, 16px),
+ "normal": clamp(15px, 1.8vw, 18px),
+ "medium": clamp(17px, 2.1vw, 22px),
+ "large": clamp(20px, 2.5vw, 32px),
+ "huge": clamp(40px, 7vw, 50px),
+ "logo": clamp(25px, 2.1vw, 30px),
+ ),
+);
+$predefined-block: (
+ padding: (
+ "content": 0 clamp(20px, 6vw, 90px),
+ "block": clamp(18px, 1.5vw, 25px) clamp(20px, 2.1vw, 32px),
+ "block-small": 17px 14px,
+ "card-content": 10px,
+ "groupbox": 10px,
+ "half-rem": .5rem,
+ ),
+ margin: (
+ "vertical-large": clamp(40px, 5vw, 90px) 0,
+ "vertical-medium": clamp(30px, 2.5vw, 45px) 0,
+ "vertical-rem": 1rem 0,
+ "horizontal-half-rem": 0 .5rem,
+ ),
+ border-radius: (
+ "small": 10px,
+ "medium": 25px,
+ "large": 30px,
+ ),
+ box-shadow: (
+ "card": 0 0 10px rgb(0 0 0 / 5%),
+ "button": 4px 4px 10px rgb(0 0 0 / 10%),
+ "button-hover": 0 0 10px rgb(0 0 0 / 15%),
+ ),
+ transform: (
+ "hover": scale(1.03),
+ ),
+ transition: (
+ "default": ease-in-out .1s,
+ "click": ease-in-out .05s,
+ ),
+);
diff --git a/src/client/packages/ui/src/components/mixins.scss b/src/client/packages/ui/src/components/mixins.scss
new file mode 100644
index 0000000..ca3466f
--- /dev/null
+++ b/src/client/packages/ui/src/components/mixins.scss
@@ -0,0 +1,9 @@
+@mixin card {
+ box-sizing: border-box;
+ max-width: 100%;
+ overflow: auto;
+ border-radius: var(--border-radius-medium);
+ background: var(--color-mono-white);
+ box-shadow: var(--box-shadow-card);
+ color: var(--color-mono-dark);
+}
diff --git a/src/client/packages/ui/src/components/variables.scss b/src/client/packages/ui/src/components/variables.scss
new file mode 100644
index 0000000..c5df589
--- /dev/null
+++ b/src/client/packages/ui/src/components/variables.scss
@@ -0,0 +1,10 @@
+$mono-dark: #3c3c3b;
+$mono-light: #f4f4f4;
+$mono-white: #fff;
+$primary: #003651;
+$primary-transparent: #00365121;
+$secondary: #25c2ff;
+$silver: #ababab;
+$red: #dc6860;
+$orange: #f0a500;
+$green: #80dc60;
diff --git a/src/client/packages/ui/src/fonts.ts b/src/client/packages/ui/src/fonts.ts
new file mode 100644
index 0000000..4eebcf9
--- /dev/null
+++ b/src/client/packages/ui/src/fonts.ts
@@ -0,0 +1,22 @@
+import type { NextFont } from "next/dist/compiled/@next/font";
+import { Open_Sans, Source_Code_Pro } from "next/font/google";
+
+export const openSans = Open_Sans({
+ subsets: ["latin"],
+ display: "swap",
+ weight: ["300", "400", "500", "600", "700"],
+ variable: "--font-family-cantarell",
+});
+
+export const sourceCodePro = Source_Code_Pro({
+ subsets: ["latin"],
+ display: "swap",
+ variable: "--font-family-source-code-pro",
+});
+
+export type Font = "open-sans" | "source-code-pro";
+
+export const fontDefinitions: Record = {
+ "open-sans": openSans,
+ "source-code-pro": sourceCodePro,
+};
diff --git a/src/client/packages/ui/src/partials/FlexPartials/FlexPartials.tsx b/src/client/packages/ui/src/partials/FlexPartials/FlexPartials.tsx
new file mode 100644
index 0000000..c58f722
--- /dev/null
+++ b/src/client/packages/ui/src/partials/FlexPartials/FlexPartials.tsx
@@ -0,0 +1,15 @@
+import type { FC } from "react";
+
+import { Flex, type FlexProps } from "../../components/Flex/Flex";
+
+export const Column: FC = (props) => {
+ return ;
+};
+
+export const GroupBox: FC = (props) => {
+ return ;
+};
+
+export const Content: FC = (props) => {
+ return ;
+};
diff --git a/src/client/packages/ui/tsconfig.json b/src/client/packages/ui/tsconfig.json
new file mode 100644
index 0000000..66c622b
--- /dev/null
+++ b/src/client/packages/ui/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@repo/typescript-config/react-library.json",
+ "compilerOptions": {
+ "outDir": "dist"
+ },
+ "include": ["global.d.ts", "src"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/src/client/packages/ui/turbo/generators/config.ts b/src/client/packages/ui/turbo/generators/config.ts
new file mode 100644
index 0000000..6244daa
--- /dev/null
+++ b/src/client/packages/ui/turbo/generators/config.ts
@@ -0,0 +1,30 @@
+import type { PlopTypes } from "@turbo/gen";
+
+// Learn more about Turborepo Generators at https://turbo.build/repo/docs/core-concepts/monorepos/code-generation
+
+export default function generator(plop: PlopTypes.NodePlopAPI): void {
+ // A simple generator to add a new React component to the internal UI library
+ plop.setGenerator("react-component", {
+ description: "Adds a new react component",
+ prompts: [
+ {
+ type: "input",
+ name: "name",
+ message: "What is the name of the component?",
+ },
+ ],
+ actions: [
+ {
+ type: "add",
+ path: "src/{{kebabCase name}}.tsx",
+ templateFile: "templates/component.hbs",
+ },
+ {
+ type: "append",
+ path: "package.json",
+ pattern: /"exports": {(?)/g,
+ template: ' "./{{kebabCase name}}": "./src/{{kebabCase name}}.tsx",',
+ },
+ ],
+ });
+}
diff --git a/src/client/packages/ui/turbo/generators/templates/component.hbs b/src/client/packages/ui/turbo/generators/templates/component.hbs
new file mode 100644
index 0000000..d968b9e
--- /dev/null
+++ b/src/client/packages/ui/turbo/generators/templates/component.hbs
@@ -0,0 +1,8 @@
+export const {{ pascalCase name }} = ({ children }: { children: React.ReactNode }) => {
+ return (
+
+
{{ pascalCase name }} Component
+ {children}
+
+ );
+};
diff --git a/src/client/packages/utils/eslint.config.js b/src/client/packages/utils/eslint.config.js
new file mode 100644
index 0000000..19170f8
--- /dev/null
+++ b/src/client/packages/utils/eslint.config.js
@@ -0,0 +1,4 @@
+import { config } from "@repo/eslint-config/react-internal";
+
+/** @type {import("eslint").Linter.Config} */
+export default config;
diff --git a/src/client/packages/utils/jest.config.ts b/src/client/packages/utils/jest.config.ts
new file mode 100644
index 0000000..4e922e9
--- /dev/null
+++ b/src/client/packages/utils/jest.config.ts
@@ -0,0 +1,12 @@
+import type { JestConfigWithTsJest } from "ts-jest";
+
+const jestConfig: JestConfigWithTsJest = {
+ preset: "ts-jest",
+ // transform: {
+ // "^.+\\.[tj]sx?$": "babel-jest",
+ // },
+ moduleFileExtensions: ["ts", "tsx", "js", "jsx"],
+ testEnvironment: "jsdom",
+};
+
+export default jestConfig;
diff --git a/src/client/packages/utils/package.json b/src/client/packages/utils/package.json
new file mode 100644
index 0000000..edf69b3
--- /dev/null
+++ b/src/client/packages/utils/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "@repo/utils",
+ "version": "0.0.0",
+ "type": "module",
+ "private": true,
+ "exports": {
+ "./useActivePathname": "./src/hooks/useActivePathname.ts",
+
+ "./ssrTranslation": "./src/i18n/index.ts",
+ "./useTranslation": "./src/i18n/client.ts",
+
+ "./bemClassNames": "./src/bemClassNames.ts",
+ "./communication": "./src/communication.ts",
+ "./endpoints": "./src/endpoints.ts",
+ "./fetchJsonFactory": "./src/fetchJsonFactory.ts",
+ "./filters": "./src/filters.ts",
+ "./getApiEndpoint": "./src/getApiEndpoint.ts",
+ "./pluralizeUnit": "./src/pluralizeUnit.ts"
+ },
+ "scripts": {
+ "lint": "eslint . --max-warnings 0 --cache",
+ "test": "jest"
+ },
+ "dependencies": {
+ "@repo/store": "workspace:*",
+ "@repo/types": "workspace:*",
+ "i18next": "^24.2.0",
+ "i18next-browser-languagedetector": "^8.0.2",
+ "i18next-resources-to-backend": "^1.2.1",
+ "react-cookie": "^7.2.2",
+ "react-i18next": "^15.4.0"
+ },
+ "devDependencies": {
+ "@repo/eslint-config": "workspace:*",
+ "@repo/typescript-config": "workspace:*"
+ }
+}
diff --git a/src/client/packages/utils/src/bemClassNames.test.ts b/src/client/packages/utils/src/bemClassNames.test.ts
new file mode 100644
index 0000000..a943cff
--- /dev/null
+++ b/src/client/packages/utils/src/bemClassNames.test.ts
@@ -0,0 +1,37 @@
+import { bemClassNames } from "./bemClassNames";
+
+describe("Utils", () => {
+ it("BEM ClassNames", () => {
+ const styles = {
+ a: "a#",
+ "a--c": "a--c#",
+ "a--c-d": "a--c-d#",
+ "a--e-f": "a--e-f#",
+ a__b: "a__b#",
+ "a__b--c": "a__b--c#",
+ "a__b--c-d": "a__b--c-d#",
+ "a__b--e-f": "a__b--e-f#",
+ "a__b--g-1": "a__b--g-1#",
+ };
+ const bem = bemClassNames(styles);
+ for (const row of [
+ [[], "a#"],
+ [["b"], "a__b#"],
+ [[{ c: true }], "a# a--c#"],
+ [[{ c: "d" }], "a# a--c-d#"],
+ [[{ c: "d", e: "f" }], "a# a--c-d# a--e-f#"],
+ [["b", { c: true }], "a__b# a__b--c#"],
+ [["b", { c: "d" }], "a__b# a__b--c-d#"],
+ [["b", { c: "d", e: "f" }], "a__b# a__b--c-d# a__b--e-f#"],
+ [["b", { c: "d", e: "f", g: 1 }], "a__b# a__b--c-d# a__b--e-f# a__b--g-1#"],
+ ] as [
+ [
+ string | number | Record,
+ Record | undefined,
+ ],
+ string,
+ ][]) {
+ expect(bem(row?.[0]?.[0], row?.[0]?.[1])).toBe(row[1]);
+ }
+ });
+});
diff --git a/src/client/packages/utils/src/bemClassNames.ts b/src/client/packages/utils/src/bemClassNames.ts
new file mode 100644
index 0000000..6305fea
--- /dev/null
+++ b/src/client/packages/utils/src/bemClassNames.ts
@@ -0,0 +1,43 @@
+export const bemClassNames = (classNamesMap: { [key: string]: string }, target?: string) => {
+ return (
+ classNameOrModifiers: string | number | Record = "",
+ modifiers: Record = {},
+ ) => {
+ let blockClassName = "";
+ if (typeof classNameOrModifiers === "object") {
+ modifiers = classNameOrModifiers;
+ } else if (typeof classNameOrModifiers === "number") {
+ blockClassName = classNameOrModifiers.toString();
+ } else {
+ blockClassName = classNameOrModifiers;
+ }
+ let baseClass = target ? target : Object.keys(classNamesMap)[0];
+ if (!baseClass) {
+ return "";
+ }
+ if (blockClassName) {
+ baseClass += `__${blockClassName}`;
+ }
+ let resultClassNames = classNamesMap[baseClass];
+ for (const modifier of Object.keys(modifiers)) {
+ const key = modifier as keyof typeof modifiers;
+ if (modifiers[key] === undefined || modifiers[key] === null || modifiers[key] === false) {
+ continue;
+ }
+ let newClassName = "";
+ if (typeof modifiers[key] === "boolean") {
+ if (modifiers[key] as unknown as boolean) {
+ newClassName = classNamesMap[`${baseClass}--${modifier}`] ?? "";
+ }
+ } else if (["string", "number"].includes(typeof modifiers[key])) {
+ newClassName = classNamesMap[`${baseClass}--${modifier}-${modifiers[key]}`] ?? "";
+ } else {
+ newClassName = classNamesMap[`${baseClass}--${modifier}`] ?? "";
+ }
+ if (newClassName) {
+ resultClassNames += ` ${newClassName}`;
+ }
+ }
+ return resultClassNames;
+ };
+};
diff --git a/src/client/packages/utils/src/communication.ts b/src/client/packages/utils/src/communication.ts
new file mode 100644
index 0000000..985c44d
--- /dev/null
+++ b/src/client/packages/utils/src/communication.ts
@@ -0,0 +1,19 @@
+export const getJson = (key: string) => jsonFetcher(key).then((response) => response.json());
+
+export const jsonFetcher = (
+ key: string,
+ method: "GET" | "POST" | "PUT" | "DELETE" = "GET",
+ payload?: object,
+) =>
+ fetch(key, {
+ method,
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ "X-CSRFToken":
+ typeof document !== "undefined"
+ ? document.cookie.replace(/(?:^|.*;\s*)csrftoken\s*=\s*([^;]*).*$|^.*$/, "$1")
+ : "",
+ },
+ body: JSON.stringify(payload),
+ });
diff --git a/src/client/packages/utils/src/endpoints.ts b/src/client/packages/utils/src/endpoints.ts
new file mode 100644
index 0000000..6612c8f
--- /dev/null
+++ b/src/client/packages/utils/src/endpoints.ts
@@ -0,0 +1,7 @@
+export enum Endpoint {
+ attributes = "core/attributes",
+ handlers = "core/handlers",
+ dataStats = "core/data-stats",
+
+ attributeChart = "core/charts/attribute",
+}
diff --git a/src/client/packages/utils/src/getApiEndpoint.test.ts b/src/client/packages/utils/src/getApiEndpoint.test.ts
new file mode 100644
index 0000000..080f408
--- /dev/null
+++ b/src/client/packages/utils/src/getApiEndpoint.test.ts
@@ -0,0 +1,26 @@
+import { Endpoint } from "./endpoints";
+import { getApiEndpoint } from "./getApiEndpoint";
+
+describe("Bridge - Endpoints", () => {
+ test("Correct route form", () => {
+ for (const endpoint of Object.values(Endpoint)) {
+ expect(endpoint[0] !== "/").toBeTruthy();
+ expect(endpoint.slice(-1) !== "/").toBeTruthy();
+ }
+ });
+
+ test("Correct route generating", () => {
+ expect(getApiEndpoint(Endpoint.dataStats)).toBe("/api/core/data-stats");
+ expect(getApiEndpoint(Endpoint.handlers, 1)).toBe("/api/core/handlers/1");
+ expect(getApiEndpoint(Endpoint.handlers, undefined, { filter: "test" })).toBe(
+ "/api/core/handlers?filter=test",
+ );
+ expect(getApiEndpoint(Endpoint.handlers, 1, { filter: "test" })).toBe(
+ "/api/core/handlers/1?filter=test",
+ );
+ expect(getApiEndpoint(Endpoint.attributes, 1, undefined, "/test")).toBe("/test/core/attributes/1");
+ expect(getApiEndpoint(Endpoint.attributes, 1, { filter: "test" }, "/test")).toBe(
+ "/test/core/attributes/1?filter=test",
+ );
+ });
+});
diff --git a/src/client/packages/utils/src/getApiEndpoint.ts b/src/client/packages/utils/src/getApiEndpoint.ts
new file mode 100644
index 0000000..a982d5c
--- /dev/null
+++ b/src/client/packages/utils/src/getApiEndpoint.ts
@@ -0,0 +1,8 @@
+// TODO: This is a ugly temporary solution
+export const getApiEndpoint = (
+ endpoint: string,
+ suffix?: string | number,
+ params?: Record,
+ prefix = "/api",
+) =>
+ `${prefix}/${endpoint}${suffix ? `/${suffix}` : ""}${params ? `?${new URLSearchParams(params).toString()}` : ""}`;
diff --git a/src/client/packages/utils/src/hooks/useActivePathname.ts b/src/client/packages/utils/src/hooks/useActivePathname.ts
new file mode 100644
index 0000000..2b7afa6
--- /dev/null
+++ b/src/client/packages/utils/src/hooks/useActivePathname.ts
@@ -0,0 +1,22 @@
+import { usePathname } from "next/navigation";
+
+import { languages } from "../i18n/settings";
+
+/**
+ * Custom hook to determine if the current pathname is active for a given href.
+ * Removes the language prefix from the pathname if rendering on server with locale rewrite.
+ */
+export const useActivePathname = (href: string) => {
+ let pn = usePathname();
+
+ for (const lang of languages) {
+ if (pn.startsWith(`/${lang}`)) {
+ pn = pn.replace(`/${lang}`, "");
+ }
+ }
+
+ return {
+ pathname: pn,
+ active: href === "/" ? pn.length < 2 : pn.startsWith(href),
+ };
+};
diff --git a/src/client/packages/utils/src/i18n/client.ts b/src/client/packages/utils/src/i18n/client.ts
new file mode 100644
index 0000000..b308dd4
--- /dev/null
+++ b/src/client/packages/utils/src/i18n/client.ts
@@ -0,0 +1,60 @@
+"use client";
+
+import { selectLocaleState } from "@repo/store/slices/settingsSlice";
+import i18next from "i18next";
+import LanguageDetector from "i18next-browser-languagedetector";
+import resourcesToBackend from "i18next-resources-to-backend";
+import { useEffect, useState } from "react";
+import { useCookies } from "react-cookie";
+import { initReactI18next, useTranslation as useTranslationOrg } from "react-i18next";
+import { useSelector } from "react-redux";
+
+import { cookieName, getOptions, languages } from "./settings";
+
+const runsOnServerSide = typeof window === "undefined";
+
+i18next
+ .use(initReactI18next)
+ .use(LanguageDetector)
+ .use(
+ resourcesToBackend(
+ (language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`),
+ ),
+ )
+ .init({
+ ...getOptions(),
+ lng: undefined, // let detect the language on client side
+ detection: {
+ order: ["htmlTag", "cookie", "navigator"],
+ },
+ preload: runsOnServerSide ? languages : [],
+ });
+
+export function useTranslation(ns: string = getOptions().defaultNS, options: { keyPrefix?: string } = {}) {
+ const lng = useSelector(selectLocaleState);
+ const [cookies, setCookie] = useCookies([cookieName]);
+ const ret = useTranslationOrg(ns, options);
+ const { i18n } = ret;
+ if (runsOnServerSide && lng && i18n.resolvedLanguage !== lng) {
+ i18n.changeLanguage(lng);
+ } else {
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ const [activeLng, setActiveLng] = useState(i18n.resolvedLanguage);
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ useEffect(() => {
+ if (activeLng === i18n.resolvedLanguage) return;
+ setActiveLng(i18n.resolvedLanguage);
+ }, [activeLng, i18n.resolvedLanguage]);
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ useEffect(() => {
+ if (!lng || i18n.resolvedLanguage === lng) return;
+ i18n.changeLanguage(lng);
+ }, [lng, i18n]);
+ // eslint-disable-next-line react-hooks/rules-of-hooks
+ useEffect(() => {
+ if (cookies.i18next === lng) return;
+ setCookie(cookieName, lng, { path: "/" });
+ }, [lng, cookies.i18next, setCookie]);
+ }
+ return ret;
+}
diff --git a/src/client/packages/utils/src/i18n/index.ts b/src/client/packages/utils/src/i18n/index.ts
new file mode 100644
index 0000000..6a8af15
--- /dev/null
+++ b/src/client/packages/utils/src/i18n/index.ts
@@ -0,0 +1,30 @@
+import { createInstance } from "i18next";
+import resourcesToBackend from "i18next-resources-to-backend";
+import { initReactI18next } from "react-i18next/initReactI18next";
+
+import { getOptions } from "./settings";
+
+const initI18next = async (lng: string, ns: string) => {
+ const i18nInstance = createInstance();
+ await i18nInstance
+ .use(initReactI18next)
+ .use(
+ resourcesToBackend(
+ (language: string, namespace: string) => import(`./locales/${language}/${namespace}.json`),
+ ),
+ )
+ .init(getOptions(lng, ns, getOptions().defaultNS));
+ return i18nInstance;
+};
+
+export async function ssrTranslation(
+ lng: string,
+ ns: string = getOptions().defaultNS,
+ options = { keyPrefix: "" },
+) {
+ const i18nextInstance = await initI18next(lng, ns);
+ return {
+ t: i18nextInstance.getFixedT(lng, Array.isArray(ns) ? ns[0] : ns, options.keyPrefix),
+ i18n: i18nextInstance,
+ };
+}
diff --git a/src/client/packages/utils/src/i18n/locales/cs/translation.json b/src/client/packages/utils/src/i18n/locales/cs/translation.json
new file mode 100644
index 0000000..44c9eb6
--- /dev/null
+++ b/src/client/packages/utils/src/i18n/locales/cs/translation.json
@@ -0,0 +1,29 @@
+{
+ "Overview": "Přehled",
+ "Inspector": "Průzkumník",
+ "Handlers": "Zařízení",
+ "Actions": "Akce",
+ "Settings": "Nastavení",
+ "Data received": "Data přijata",
+ "Now": "Nyní",
+ "Never": "Nikdy",
+ "Handler ID": "ID zařízení",
+ "Attributes": "Atributy",
+ "Zoom out": "Oddálit",
+ "Show data for date": "Zobrazit data pro den",
+ "Displayed attributes": "Zobrazené atributy",
+ "Reset": "Resetovat",
+ "Confirm": "Potvrdit",
+ "New action editor": "Nový editor akcí",
+ "is currently in development": "je aktuálně ve vývoji",
+ "Custom label": "Vlastní popis",
+ "Unit": "Jednotka",
+ "Icon": "Ikona",
+ "Save": "Uložit",
+ "Stored attributes": "Uložené atributy",
+ "Available attributes": "Dostupné atributy",
+ "Do you want to delete attribute": "Opravdu chcete smazat atribut",
+ "Decimal places rounding": "Zaokrouhlení desetinných míst",
+ "Chart color": "Barva grafu",
+ "Select icon": "Vyberte ikonu"
+}
diff --git a/src/client/packages/utils/src/i18n/locales/en/translation.json b/src/client/packages/utils/src/i18n/locales/en/translation.json
new file mode 100644
index 0000000..c99b08b
--- /dev/null
+++ b/src/client/packages/utils/src/i18n/locales/en/translation.json
@@ -0,0 +1,29 @@
+{
+ "Overview": "Overview",
+ "Inspector": "Inspector",
+ "Handlers": "Handlers",
+ "Actions": "Actions",
+ "Settings": "Settings",
+ "Data received": "Data received",
+ "Now": "Now",
+ "Never": "Never",
+ "Handler ID": "Handler ID",
+ "Attributes": "Attributes",
+ "Zoom out": "Zoom out",
+ "Show data for date": "Show data for date",
+ "Displayed attributes": "Displayed attributes",
+ "Reset": "Reset",
+ "Confirm": "Confirm",
+ "New action editor": "New action editor",
+ "is currently in development": "is currently in development",
+ "Custom label": "Custom label",
+ "Unit": "Unit",
+ "Icon": "Icon",
+ "Save": "Save",
+ "Stored attributes": "Stored attributes",
+ "Available attributes": "Available attributes",
+ "Do you want to delete attribute": "Do you want to delete attribute",
+ "Decimal places rounding": "Decimal places rounding",
+ "Chart color": "Chart color",
+ "Select icon": "Select icon"
+}
diff --git a/src/client/packages/utils/src/i18n/settings.ts b/src/client/packages/utils/src/i18n/settings.ts
new file mode 100644
index 0000000..ad54130
--- /dev/null
+++ b/src/client/packages/utils/src/i18n/settings.ts
@@ -0,0 +1,16 @@
+export const fallbackLng = "en";
+export const languages = [fallbackLng, "cs"];
+export const defaultNS = "translation";
+export const cookieName = "i18next";
+
+export function getOptions(lng = fallbackLng, ns = defaultNS, fallbackNS = defaultNS) {
+ return {
+ // debug: true,
+ supportedLngs: languages,
+ fallbackLng,
+ lng,
+ fallbackNS,
+ defaultNS,
+ ns: [ns, fallbackNS],
+ };
+}
diff --git a/src/client/packages/utils/src/pluralizeUnit.ts b/src/client/packages/utils/src/pluralizeUnit.ts
new file mode 100644
index 0000000..411c32f
--- /dev/null
+++ b/src/client/packages/utils/src/pluralizeUnit.ts
@@ -0,0 +1,21 @@
+/**
+ * Returns the correct form of a unit based on the value.
+ * TODO: Unit test
+ * @param value The value to be checked
+ * @param config Configuration object
+ */
+export default function pluralizeUnit(
+ value: number,
+ config: { zero?: string; singular?: string; semiSingular?: string; plural?: string } = {},
+) {
+ if (value === 0) {
+ return config.zero || config.plural || config.singular;
+ }
+ if (value === 1) {
+ return config.singular || config.plural;
+ }
+ if (value > 1 && value < 5) {
+ return config.semiSingular || config.plural || config.singular;
+ }
+ return config.plural || config.semiSingular || config.singular;
+}
diff --git a/src/client/packages/utils/tsconfig.json b/src/client/packages/utils/tsconfig.json
new file mode 100644
index 0000000..565a98e
--- /dev/null
+++ b/src/client/packages/utils/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@repo/typescript-config/react-library.json",
+ "compilerOptions": {
+ "outDir": "dist"
+ },
+ "include": ["global.d.ts", "src"],
+ "exclude": ["node_modules", "dist"],
+}
diff --git a/src/client/pnpm-lock.yaml b/src/client/pnpm-lock.yaml
new file mode 100644
index 0000000..445a5f2
--- /dev/null
+++ b/src/client/pnpm-lock.yaml
@@ -0,0 +1,8766 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@reduxjs/toolkit':
+ specifier: ^2.5.0
+ version: 2.5.0(react-redux@9.2.0(@types/react@19.0.2)(react@19.2.3)(redux@5.0.1))(react@19.2.3)
+ lodash.debounce:
+ specifier: ^4.0.8
+ version: 4.0.8
+ next:
+ specifier: ^15.5.7
+ version: 15.5.7(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.83.0)
+ next-redux-wrapper:
+ specifier: ^8.1.0
+ version: 8.1.0(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.83.0))(react-redux@9.2.0(@types/react@19.0.2)(react@19.2.3)(redux@5.0.1))(react@19.2.3)
+ react:
+ specifier: ^19.0.0
+ version: 19.2.3
+ react-dom:
+ specifier: ^19.0.0
+ version: 19.2.3(react@19.2.3)
+ react-redux:
+ specifier: ^9.2.0
+ version: 9.2.0(@types/react@19.0.2)(react@19.2.3)(redux@5.0.1)
+ sass:
+ specifier: ^1.83.0
+ version: 1.83.0
+ swr:
+ specifier: ^2.3.8
+ version: 2.3.8(react@19.2.3)
+ swr-models:
+ specifier: ^1.0.2
+ version: 1.0.2(react@19.2.3)(swr@2.3.8(react@19.2.3))
+ turbo:
+ specifier: ^2.5.8
+ version: 2.5.8
+ typescript:
+ specifier: ^5.9.3
+ version: 5.9.3
+ devDependencies:
+ '@biomejs/biome':
+ specifier: ^2.3.8
+ version: 2.3.8
+ '@eslint/js':
+ specifier: ^9.17.0
+ version: 9.39.2
+ '@next/eslint-plugin-next':
+ specifier: ^15.1.0
+ version: 15.5.9
+ '@types/jest':
+ specifier: ^29.5.14
+ version: 29.5.14
+ '@types/lodash.debounce':
+ specifier: ^4.0.9
+ version: 4.0.9
+ '@types/react':
+ specifier: ^19.0.2
+ version: 19.0.2
+ '@types/react-dom':
+ specifier: ^19.0.2
+ version: 19.0.2(@types/react@19.0.2)
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^8.15.0
+ version: 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)
+ '@typescript-eslint/parser':
+ specifier: ^8.15.0
+ version: 8.52.0(eslint@9.39.2)(typescript@5.9.3)
+ eslint:
+ specifier: ^9.39.1
+ version: 9.39.2
+ eslint-config-prettier:
+ specifier: ^10.1.8
+ version: 10.1.8(eslint@9.39.2)
+ eslint-plugin-only-warn:
+ specifier: ^1.1.0
+ version: 1.1.0
+ eslint-plugin-react:
+ specifier: ^7.37.5
+ version: 7.37.5(eslint@9.39.2)
+ eslint-plugin-react-hooks:
+ specifier: ^7.0.1
+ version: 7.0.1(eslint@9.39.2)
+ eslint-plugin-simple-import-sort:
+ specifier: ^12.1.1
+ version: 12.1.1(eslint@9.39.2)
+ eslint-plugin-turbo:
+ specifier: ^2.6.3
+ version: 2.7.3(eslint@9.39.2)(turbo@2.5.8)
+ globals:
+ specifier: ^15.12.0
+ version: 15.15.0
+ jest:
+ specifier: ^29.7.0
+ version: 29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ jest-environment-jsdom:
+ specifier: ^29.7.0
+ version: 29.7.0
+ only-allow:
+ specifier: ^1.2.2
+ version: 1.2.2
+ prettier:
+ specifier: ^3.4.2
+ version: 3.4.2
+ stylelint:
+ specifier: ^16.10.0
+ version: 16.10.0(typescript@5.9.3)
+ stylelint-config-prettier-scss:
+ specifier: ^1.0.0
+ version: 1.0.0(stylelint@16.10.0(typescript@5.9.3))
+ stylelint-config-property-sort-order-smacss:
+ specifier: ^10.0.0
+ version: 10.0.0(stylelint@16.10.0(typescript@5.9.3))
+ stylelint-config-standard-scss:
+ specifier: ^13.1.0
+ version: 13.1.0(postcss@8.5.6)(stylelint@16.10.0(typescript@5.9.3))
+ stylelint-order:
+ specifier: ^6.0.4
+ version: 6.0.4(stylelint@16.10.0(typescript@5.9.3))
+ ts-jest:
+ specifier: ^29.2.5
+ version: 29.2.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest@29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3)))(typescript@5.9.3)
+ typescript-eslint:
+ specifier: ^8.49.0
+ version: 8.52.0(eslint@9.39.2)(typescript@5.9.3)
+
+ apps/contwatch-client:
+ dependencies:
+ '@dnd-kit/core':
+ specifier: ^6.3.1
+ version: 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@dnd-kit/modifiers':
+ specifier: ^9.0.0
+ version: 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
+ '@dnd-kit/sortable':
+ specifier: ^10.0.0
+ version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
+ '@dnd-kit/utilities':
+ specifier: ^3.2.2
+ version: 3.2.2(react@19.2.3)
+ '@repo/store':
+ specifier: workspace:*
+ version: link:../../packages/store
+ '@repo/types':
+ specifier: workspace:*
+ version: link:../../packages/types
+ '@repo/ui':
+ specifier: workspace:*
+ version: link:../../packages/ui
+ '@repo/utils':
+ specifier: workspace:*
+ version: link:../../packages/utils
+ '@xyflow/react':
+ specifier: ^12.3.6
+ version: 12.3.6(@types/react@19.0.2)(immer@10.2.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@xyflow/system':
+ specifier: ^0.0.47
+ version: 0.0.47
+ chart.js:
+ specifier: ^4.5.0
+ version: 4.5.0
+ chartjs-adapter-date-fns:
+ specifier: ^3.0.0
+ version: 3.0.0(chart.js@4.5.0)(date-fns@4.1.0)
+ chartjs-plugin-zoom:
+ specifier: ^2.2.0
+ version: 2.2.0(chart.js@4.5.0)
+ luxon:
+ specifier: ^3.5.0
+ version: 3.5.0
+ negotiator:
+ specifier: ^1.0.0
+ version: 1.0.0
+ react-chartjs-2:
+ specifier: ^5.3.0
+ version: 5.3.0(chart.js@4.5.0)(react@19.2.3)
+ socket.io-client:
+ specifier: ^4.8.1
+ version: 4.8.1
+ devDependencies:
+ '@repo/eslint-config':
+ specifier: workspace:*
+ version: link:../../packages/eslint-config
+ '@repo/typescript-config':
+ specifier: workspace:*
+ version: link:../../packages/typescript-config
+ '@types/luxon':
+ specifier: ^3.4.2
+ version: 3.4.2
+ '@types/negotiator':
+ specifier: ^0.6.3
+ version: 0.6.4
+
+ apps/docs:
+ dependencies:
+ '@repo/ui':
+ specifier: workspace:*
+ version: link:../../packages/ui
+ devDependencies:
+ '@repo/eslint-config':
+ specifier: workspace:*
+ version: link:../../packages/eslint-config
+ '@repo/typescript-config':
+ specifier: workspace:*
+ version: link:../../packages/typescript-config
+ '@types/node':
+ specifier: ^22
+ version: 22.19.5
+ typescript:
+ specifier: ^5.5.4
+ version: 5.9.3
+
+ packages/eslint-config: {}
+
+ packages/store:
+ dependencies:
+ '@repo/types':
+ specifier: workspace:*
+ version: link:../types
+ devDependencies:
+ '@repo/eslint-config':
+ specifier: workspace:*
+ version: link:../eslint-config
+
+ packages/types: {}
+
+ packages/typescript-config: {}
+
+ packages/ui:
+ dependencies:
+ '@repo/store':
+ specifier: workspace:*
+ version: link:../store
+ '@repo/typescript-config':
+ specifier: workspace:*
+ version: link:../typescript-config
+ '@repo/utils':
+ specifier: workspace:*
+ version: link:../utils
+ csstype:
+ specifier: ^3.1.3
+ version: 3.2.3
+ react-spinners:
+ specifier: ^0.15.0
+ version: 0.15.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ devDependencies:
+ '@repo/eslint-config':
+ specifier: workspace:*
+ version: link:../eslint-config
+ '@repo/types':
+ specifier: workspace:*
+ version: link:../types
+ '@testing-library/dom':
+ specifier: ^10.4.0
+ version: 10.4.0
+ '@testing-library/jest-dom':
+ specifier: ^6.6.3
+ version: 6.6.3
+ '@testing-library/react':
+ specifier: ^16.1.0
+ version: 16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@testing-library/user-event':
+ specifier: ^14.5.2
+ version: 14.5.2(@testing-library/dom@10.4.0)
+ '@turbo/gen':
+ specifier: ^1.12.4
+ version: 1.13.4(@types/node@22.19.5)(typescript@5.9.3)
+ '@types/node':
+ specifier: ^22
+ version: 22.19.5
+ '@types/redux-mock-store':
+ specifier: ^1.5.0
+ version: 1.5.0
+ identity-obj-proxy:
+ specifier: ^3.0.0
+ version: 3.0.0
+ jest:
+ specifier: ^29.7.0
+ version: 29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ jest-environment-jsdom:
+ specifier: ^29.7.0
+ version: 29.7.0
+ redux-mock-store:
+ specifier: ^1.5.5
+ version: 1.5.5(redux@5.0.1)
+
+ packages/utils:
+ dependencies:
+ '@repo/store':
+ specifier: workspace:*
+ version: link:../store
+ '@repo/types':
+ specifier: workspace:*
+ version: link:../types
+ i18next:
+ specifier: ^24.2.0
+ version: 24.2.0(typescript@5.9.3)
+ i18next-browser-languagedetector:
+ specifier: ^8.0.2
+ version: 8.0.2
+ i18next-resources-to-backend:
+ specifier: ^1.2.1
+ version: 1.2.1
+ react-cookie:
+ specifier: ^7.2.2
+ version: 7.2.2(@types/react@19.0.2)(react@19.2.3)
+ react-i18next:
+ specifier: ^15.4.0
+ version: 15.4.0(i18next@24.2.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ devDependencies:
+ '@repo/eslint-config':
+ specifier: workspace:*
+ version: link:../eslint-config
+ '@repo/typescript-config':
+ specifier: workspace:*
+ version: link:../typescript-config
+
+packages:
+
+ '@adobe/css-tools@4.4.4':
+ resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
+
+ '@babel/code-frame@7.27.1':
+ resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.5':
+ resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.5':
+ resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.5':
+ resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.27.2':
+ resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.27.1':
+ resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.3':
+ resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.27.1':
+ resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.4':
+ resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-syntax-async-generators@7.8.4':
+ resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-bigint@7.8.3':
+ resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-class-properties@7.12.13':
+ resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-class-static-block@7.14.5':
+ resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-attributes@7.27.1':
+ resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-import-meta@7.10.4':
+ resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-json-strings@7.8.3':
+ resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-jsx@7.27.1':
+ resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-logical-assignment-operators@7.10.4':
+ resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3':
+ resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-numeric-separator@7.10.4':
+ resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-object-rest-spread@7.8.3':
+ resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-optional-catch-binding@7.8.3':
+ resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-optional-chaining@7.8.3':
+ resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-private-property-in-object@7.14.5':
+ resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-top-level-await@7.14.5':
+ resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-syntax-typescript@7.27.1':
+ resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime-corejs3@7.28.4':
+ resolution: {integrity: sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/runtime@7.28.4':
+ resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.27.2':
+ resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.5':
+ resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+ engines: {node: '>=6.9.0'}
+
+ '@bcoe/v8-coverage@0.2.3':
+ resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+
+ '@biomejs/biome@2.3.8':
+ resolution: {integrity: sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA==}
+ engines: {node: '>=14.21.3'}
+ hasBin: true
+
+ '@biomejs/cli-darwin-arm64@2.3.8':
+ resolution: {integrity: sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@biomejs/cli-darwin-x64@2.3.8':
+ resolution: {integrity: sha512-lUDQ03D7y/qEao7RgdjWVGCu+BLYadhKTm40HkpJIi6kn8LSv5PAwRlew/DmwP4YZ9ke9XXoTIQDO1vAnbRZlA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@biomejs/cli-linux-arm64-musl@2.3.8':
+ resolution: {integrity: sha512-PShR4mM0sjksUMyxbyPNMxoKFPVF48fU8Qe8Sfx6w6F42verbwRLbz+QiKNiDPRJwUoMG1nPM50OBL3aOnTevA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-arm64@2.3.8':
+ resolution: {integrity: sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64-musl@2.3.8':
+ resolution: {integrity: sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64@2.3.8':
+ resolution: {integrity: sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-win32-arm64@2.3.8':
+ resolution: {integrity: sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@biomejs/cli-win32-x64@2.3.8':
+ resolution: {integrity: sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [win32]
+
+ '@cspotcode/source-map-support@0.8.1':
+ resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+ engines: {node: '>=12'}
+
+ '@csstools/css-parser-algorithms@3.0.5':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-tokenizer@3.0.4':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+ engines: {node: '>=18'}
+
+ '@csstools/media-query-list-parser@3.0.1':
+ resolution: {integrity: sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.1
+ '@csstools/css-tokenizer': ^3.0.1
+
+ '@csstools/selector-specificity@4.0.0':
+ resolution: {integrity: sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ postcss-selector-parser: ^6.1.0
+
+ '@dnd-kit/accessibility@3.1.1':
+ resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@dnd-kit/core@6.3.1':
+ resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
+ '@dnd-kit/modifiers@9.0.0':
+ resolution: {integrity: sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==}
+ peerDependencies:
+ '@dnd-kit/core': ^6.3.0
+ react: '>=16.8.0'
+
+ '@dnd-kit/sortable@10.0.0':
+ resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
+ peerDependencies:
+ '@dnd-kit/core': ^6.3.0
+ react: '>=16.8.0'
+
+ '@dnd-kit/utilities@3.2.2':
+ resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
+ peerDependencies:
+ react: '>=16.8.0'
+
+ '@dual-bundle/import-meta-resolve@4.2.1':
+ resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==}
+
+ '@emnapi/runtime@1.8.1':
+ resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
+
+ '@eslint-community/eslint-utils@4.9.1':
+ resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.2':
+ resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.21.1':
+ resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.4.2':
+ resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.17.0':
+ resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.3':
+ resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.39.2':
+ resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.7':
+ resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.4.1':
+ resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.7':
+ resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@img/colour@1.0.0':
+ resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
+ engines: {node: '>=18'}
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-darwin-x64@0.34.5':
+ resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linux-arm64@0.34.5':
+ resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linux-arm@0.34.5':
+ resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@img/sharp-linux-s390x@0.34.5':
+ resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [s390x]
+ os: [linux]
+
+ '@img/sharp-linux-x64@0.34.5':
+ resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [linux]
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [linux]
+
+ '@img/sharp-wasm32@0.34.5':
+ resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [wasm32]
+
+ '@img/sharp-win32-arm64@0.34.5':
+ resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@img/sharp-win32-ia32@0.34.5':
+ resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@img/sharp-win32-x64@0.34.5':
+ resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@inquirer/external-editor@1.0.3':
+ resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@types/node': '>=18'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@istanbuljs/load-nyc-config@1.1.0':
+ resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
+ engines: {node: '>=8'}
+
+ '@istanbuljs/schema@0.1.3':
+ resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
+ engines: {node: '>=8'}
+
+ '@jest/console@29.7.0':
+ resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/core@29.7.0':
+ resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ '@jest/environment@29.7.0':
+ resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/expect-utils@29.7.0':
+ resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/expect@29.7.0':
+ resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/fake-timers@29.7.0':
+ resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/globals@29.7.0':
+ resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/reporters@29.7.0':
+ resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ '@jest/schemas@29.6.3':
+ resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/source-map@29.6.3':
+ resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/test-result@29.7.0':
+ resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/test-sequencer@29.7.0':
+ resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/transform@29.7.0':
+ resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jest/types@29.6.3':
+ resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/sourcemap-codec@1.5.5':
+ resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@jridgewell/trace-mapping@0.3.9':
+ resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
+ '@kurkle/color@0.3.4':
+ resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
+
+ '@next/env@15.5.7':
+ resolution: {integrity: sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg==}
+
+ '@next/eslint-plugin-next@15.5.9':
+ resolution: {integrity: sha512-kUzXx0iFiXw27cQAViE1yKWnz/nF8JzRmwgMRTMh8qMY90crNsdXJRh2e+R0vBpFR3kk1yvAR7wev7+fCCb79Q==}
+
+ '@next/swc-darwin-arm64@15.5.7':
+ resolution: {integrity: sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@next/swc-darwin-x64@15.5.7':
+ resolution: {integrity: sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@next/swc-linux-arm64-gnu@15.5.7':
+ resolution: {integrity: sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-arm64-musl@15.5.7':
+ resolution: {integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@next/swc-linux-x64-gnu@15.5.7':
+ resolution: {integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-linux-x64-musl@15.5.7':
+ resolution: {integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
+ '@next/swc-win32-arm64-msvc@15.5.7':
+ resolution: {integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@next/swc-win32-x64-msvc@15.5.7':
+ resolution: {integrity: sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@nodelib/fs.scandir@2.1.5':
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.stat@2.0.5':
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+
+ '@nodelib/fs.walk@1.2.8':
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+
+ '@parcel/watcher-android-arm64@2.5.1':
+ resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [android]
+
+ '@parcel/watcher-darwin-arm64@2.5.1':
+ resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@parcel/watcher-darwin-x64@2.5.1':
+ resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1':
+ resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1':
+ resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1':
+ resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@parcel/watcher-linux-x64-musl@2.5.1':
+ resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [linux]
+
+ '@parcel/watcher-win32-arm64@2.5.1':
+ resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@parcel/watcher-win32-x64@2.5.1':
+ resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
+ engines: {node: '>= 10.0.0'}
+ cpu: [x64]
+ os: [win32]
+
+ '@parcel/watcher@2.5.1':
+ resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
+ engines: {node: '>= 10.0.0'}
+
+ '@reduxjs/toolkit@2.5.0':
+ resolution: {integrity: sha512-awNe2oTodsZ6LmRqmkFhtb/KH03hUhxOamEQy411m3Njj3BbFvoBovxo4Q1cBWnV1ErprVj9MlF0UPXkng0eyg==}
+ peerDependencies:
+ react: ^16.9.0 || ^17.0.0 || ^18 || ^19
+ react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
+ peerDependenciesMeta:
+ react:
+ optional: true
+ react-redux:
+ optional: true
+
+ '@sinclair/typebox@0.27.8':
+ resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
+
+ '@sinonjs/commons@3.0.1':
+ resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==}
+
+ '@sinonjs/fake-timers@10.3.0':
+ resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
+
+ '@socket.io/component-emitter@3.1.2':
+ resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+
+ '@swc/helpers@0.5.15':
+ resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
+
+ '@testing-library/dom@10.4.0':
+ resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
+ engines: {node: '>=18'}
+
+ '@testing-library/jest-dom@6.6.3':
+ resolution: {integrity: sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==}
+ engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
+
+ '@testing-library/react@16.1.0':
+ resolution: {integrity: sha512-Q2ToPvg0KsVL0ohND9A3zLJWcOXXcO8IDu3fj11KhNt0UlCWyFyvnCIBkd12tidB2lkiVRG8VFqdhcqhqnAQtg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@testing-library/dom': ^10.0.0
+ '@types/react': ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^18.0.0 || ^19.0.0
+ react: ^18.0.0 || ^19.0.0
+ react-dom: ^18.0.0 || ^19.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ '@types/react-dom':
+ optional: true
+
+ '@testing-library/user-event@14.5.2':
+ resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==}
+ engines: {node: '>=12', npm: '>=6'}
+ peerDependencies:
+ '@testing-library/dom': '>=7.21.4'
+
+ '@tootallnate/once@2.0.0':
+ resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
+ engines: {node: '>= 10'}
+
+ '@tootallnate/quickjs-emscripten@0.23.0':
+ resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
+
+ '@tsconfig/node10@1.0.12':
+ resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==}
+
+ '@tsconfig/node12@1.0.11':
+ resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
+ '@tsconfig/node14@1.0.3':
+ resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
+ '@tsconfig/node16@1.0.4':
+ resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
+ '@turbo/gen@1.13.4':
+ resolution: {integrity: sha512-PK38N1fHhDUyjLi0mUjv0RbX0xXGwDLQeRSGsIlLcVpP1B5fwodSIwIYXc9vJok26Yne94BX5AGjueYsUT3uUw==}
+ hasBin: true
+
+ '@turbo/workspaces@1.13.4':
+ resolution: {integrity: sha512-3uYg2b5TWCiupetbDFMbBFMHl33xQTvp5DNg0fZSYal73Z9AlFH9yWabHWMYw6ywmwM1evkYRpTVA2n7GgqT5A==}
+ hasBin: true
+
+ '@types/aria-query@5.0.4':
+ resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
+ '@types/cookie@0.6.0':
+ resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
+
+ '@types/d3-color@3.1.3':
+ resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
+
+ '@types/d3-drag@3.0.7':
+ resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==}
+
+ '@types/d3-interpolate@3.0.4':
+ resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
+
+ '@types/d3-selection@3.0.11':
+ resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==}
+
+ '@types/d3-transition@3.0.9':
+ resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==}
+
+ '@types/d3-zoom@3.0.8':
+ resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==}
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/glob@7.2.0':
+ resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
+
+ '@types/graceful-fs@4.1.9':
+ resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
+
+ '@types/hammerjs@2.0.46':
+ resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==}
+
+ '@types/hoist-non-react-statics@3.3.7':
+ resolution: {integrity: sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==}
+ peerDependencies:
+ '@types/react': '*'
+
+ '@types/inquirer@6.5.0':
+ resolution: {integrity: sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==}
+
+ '@types/istanbul-lib-coverage@2.0.6':
+ resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
+
+ '@types/istanbul-lib-report@3.0.3':
+ resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
+
+ '@types/istanbul-reports@3.0.4':
+ resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
+
+ '@types/jest@29.5.14':
+ resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==}
+
+ '@types/jsdom@20.0.1':
+ resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ '@types/lodash.debounce@4.0.9':
+ resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==}
+
+ '@types/lodash@4.17.21':
+ resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==}
+
+ '@types/luxon@3.4.2':
+ resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
+
+ '@types/minimatch@6.0.0':
+ resolution: {integrity: sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==}
+ deprecated: This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed.
+
+ '@types/negotiator@0.6.4':
+ resolution: {integrity: sha512-elf6BsTq+AkyNsb2h5cGNst2Mc7dPliVoAPm1fXglC/BM3f2pFA40BaSSv3E5lyHteEawVKLP+8TwiY1DMNb3A==}
+
+ '@types/node@22.19.5':
+ resolution: {integrity: sha512-HfF8+mYcHPcPypui3w3mvzuIErlNOh2OAG+BCeBZCEwyiD5ls2SiCwEyT47OELtf7M3nHxBdu0FsmzdKxkN52Q==}
+
+ '@types/react-dom@19.0.2':
+ resolution: {integrity: sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==}
+ peerDependencies:
+ '@types/react': ^19.0.0
+
+ '@types/react@19.0.2':
+ resolution: {integrity: sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==}
+
+ '@types/redux-mock-store@1.5.0':
+ resolution: {integrity: sha512-jcscBazm6j05Hs6xYCca6psTUBbFT2wqMxT7wZEHAYFxHB/I8jYk7d5msrHUlDiSL02HdTqTmkK2oIV8i3C8DA==}
+
+ '@types/stack-utils@2.0.3':
+ resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
+
+ '@types/through@0.0.33':
+ resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==}
+
+ '@types/tinycolor2@1.4.6':
+ resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==}
+
+ '@types/tough-cookie@4.0.5':
+ resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
+
+ '@types/use-sync-external-store@0.0.6':
+ resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
+
+ '@types/yargs-parser@21.0.3':
+ resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
+
+ '@types/yargs@17.0.35':
+ resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
+
+ '@typescript-eslint/eslint-plugin@8.52.0':
+ resolution: {integrity: sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^8.52.0
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/parser@8.52.0':
+ resolution: {integrity: sha512-iIACsx8pxRnguSYhHiMn2PvhvfpopO9FXHyn1mG5txZIsAaB6F0KwbFnUQN3KCiG3Jcuad/Cao2FAs1Wp7vAyg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/project-service@8.52.0':
+ resolution: {integrity: sha512-xD0MfdSdEmeFa3OmVqonHi+Cciab96ls1UhIF/qX/O/gPu5KXD0bY9lu33jj04fjzrXHcuvjBcBC+D3SNSadaw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/scope-manager@8.52.0':
+ resolution: {integrity: sha512-ixxqmmCcc1Nf8S0mS0TkJ/3LKcC8mruYJPOU6Ia2F/zUUR4pApW7LzrpU3JmtePbRUTes9bEqRc1Gg4iyRnDzA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/tsconfig-utils@8.52.0':
+ resolution: {integrity: sha512-jl+8fzr/SdzdxWJznq5nvoI7qn2tNYV/ZBAEcaFMVXf+K6jmXvAFrgo/+5rxgnL152f//pDEAYAhhBAZGrVfwg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/type-utils@8.52.0':
+ resolution: {integrity: sha512-JD3wKBRWglYRQkAtsyGz1AewDu3mTc7NtRjR/ceTyGoPqmdS5oCdx/oZMWD5Zuqmo6/MpsYs0wp6axNt88/2EQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/types@8.52.0':
+ resolution: {integrity: sha512-LWQV1V4q9V4cT4H5JCIx3481iIFxH1UkVk+ZkGGAV1ZGcjGI9IoFOfg3O6ywz8QqCDEp7Inlg6kovMofsNRaGg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@typescript-eslint/typescript-estree@8.52.0':
+ resolution: {integrity: sha512-XP3LClsCc0FsTK5/frGjolyADTh3QmsLp6nKd476xNI9CsSsLnmn4f0jrzNoAulmxlmNIpeXuHYeEQv61Q6qeQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/utils@8.52.0':
+ resolution: {integrity: sha512-wYndVMWkweqHpEpwPhwqE2lnD2DxC6WVLupU/DOt/0/v+/+iQbbzO3jOHjmBMnhu0DgLULvOaU4h4pwHYi2oRQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ '@typescript-eslint/visitor-keys@8.52.0':
+ resolution: {integrity: sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@xyflow/react@12.3.6':
+ resolution: {integrity: sha512-9GS+cz8hDZahpvTrVCmySAEgKUL8oN4b2q1DluHrKtkqhAMWfH2s7kblhbM4Y4Y4SUnH2lt4drXKZ/4/Lot/2Q==}
+ peerDependencies:
+ react: '>=17'
+ react-dom: '>=17'
+
+ '@xyflow/system@0.0.47':
+ resolution: {integrity: sha512-aUXJPIvsCFxGX70ccRG8LPsR+A8ExYXfh/noYNpqn8udKerrLdSHxMG2VsvUrQ1PGex10fOpbJwFU4A+I/Xv8w==}
+
+ abab@2.0.6:
+ resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
+ deprecated: Use your platform's native atob() and btoa() methods instead
+
+ acorn-globals@7.0.1:
+ resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn-walk@8.3.4:
+ resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==}
+ engines: {node: '>=0.4.0'}
+
+ acorn@8.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ agent-base@6.0.2:
+ resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
+ engines: {node: '>= 6.0.0'}
+
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
+ aggregate-error@3.1.0:
+ resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
+ engines: {node: '>=8'}
+
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ ajv@8.17.1:
+ resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
+
+ ansi-escapes@4.3.2:
+ resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
+ engines: {node: '>=8'}
+
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
+ ansi-styles@3.2.1:
+ resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+ engines: {node: '>=4'}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ ansi-styles@5.2.0:
+ resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+ engines: {node: '>=10'}
+
+ anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+
+ arg@4.1.3:
+ resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+
+ argparse@1.0.10:
+ resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ aria-query@5.3.0:
+ resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
+
+ aria-query@5.3.2:
+ resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
+ engines: {node: '>= 0.4'}
+
+ array-buffer-byte-length@1.0.2:
+ resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+ engines: {node: '>= 0.4'}
+
+ array-includes@3.1.9:
+ resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
+ engines: {node: '>= 0.4'}
+
+ array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+
+ array.prototype.findlast@1.2.5:
+ resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flat@1.3.3:
+ resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.flatmap@1.3.3:
+ resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
+ engines: {node: '>= 0.4'}
+
+ array.prototype.tosorted@1.1.4:
+ resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+ engines: {node: '>= 0.4'}
+
+ arraybuffer.prototype.slice@1.0.4:
+ resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+ engines: {node: '>= 0.4'}
+
+ ast-types@0.13.4:
+ resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
+ engines: {node: '>=4'}
+
+ astral-regex@2.0.0:
+ resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
+ engines: {node: '>=8'}
+
+ async-function@1.0.0:
+ resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+ engines: {node: '>= 0.4'}
+
+ async@3.2.6:
+ resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
+
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+ available-typed-arrays@1.0.7:
+ resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+ engines: {node: '>= 0.4'}
+
+ babel-jest@29.7.0:
+ resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ '@babel/core': ^7.8.0
+
+ babel-plugin-istanbul@6.1.1:
+ resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==}
+ engines: {node: '>=8'}
+
+ babel-plugin-jest-hoist@29.6.3:
+ resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ babel-preset-current-node-syntax@1.2.0:
+ resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==}
+ peerDependencies:
+ '@babel/core': ^7.0.0 || ^8.0.0-0
+
+ babel-preset-jest@29.6.3:
+ resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ balanced-match@2.0.0:
+ resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==}
+
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
+ baseline-browser-mapping@2.9.14:
+ resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==}
+ hasBin: true
+
+ basic-ftp@5.1.0:
+ resolution: {integrity: sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw==}
+ engines: {node: '>=10.0.0'}
+
+ bl@4.1.0:
+ resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
+
+ brace-expansion@1.1.12:
+ resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+
+ brace-expansion@2.0.2:
+ resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+
+ braces@3.0.3:
+ resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+ engines: {node: '>=8'}
+
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ bs-logger@0.2.6:
+ resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==}
+ engines: {node: '>= 6'}
+
+ bser@2.1.1:
+ resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
+
+ buffer-from@1.1.2:
+ resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+ buffer@5.7.1:
+ resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+
+ call-bind-apply-helpers@1.0.2:
+ resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+ engines: {node: '>= 0.4'}
+
+ call-bind@1.0.8:
+ resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+ engines: {node: '>= 0.4'}
+
+ call-bound@1.0.4:
+ resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+ engines: {node: '>= 0.4'}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ camel-case@3.0.0:
+ resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==}
+
+ camelcase@5.3.1:
+ resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
+ engines: {node: '>=6'}
+
+ camelcase@6.3.0:
+ resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+ engines: {node: '>=10'}
+
+ caniuse-lite@1.0.30001763:
+ resolution: {integrity: sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==}
+
+ chalk@2.4.2:
+ resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+ engines: {node: '>=4'}
+
+ chalk@3.0.0:
+ resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}
+ engines: {node: '>=8'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ change-case@3.1.0:
+ resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==}
+
+ char-regex@1.0.2:
+ resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
+ engines: {node: '>=10'}
+
+ chardet@0.7.0:
+ resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
+
+ chardet@2.1.1:
+ resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
+
+ chart.js@4.5.0:
+ resolution: {integrity: sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==}
+ engines: {pnpm: '>=8'}
+
+ chartjs-adapter-date-fns@3.0.0:
+ resolution: {integrity: sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==}
+ peerDependencies:
+ chart.js: '>=2.8.0'
+ date-fns: '>=2.0.0'
+
+ chartjs-plugin-zoom@2.2.0:
+ resolution: {integrity: sha512-in6kcdiTlP6npIVLMd4zXZ08PDUXC52gZ4FAy5oyjk1zX3gKarXMAof7B9eFiisf9WOC3bh2saHg+J5WtLXZeA==}
+ peerDependencies:
+ chart.js: '>=3.2.0'
+
+ chokidar@4.0.3:
+ resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
+ engines: {node: '>= 14.16.0'}
+
+ ci-info@3.9.0:
+ resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
+ engines: {node: '>=8'}
+
+ cjs-module-lexer@1.4.3:
+ resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
+
+ classcat@5.0.5:
+ resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==}
+
+ clean-stack@2.2.0:
+ resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
+ engines: {node: '>=6'}
+
+ cli-cursor@3.1.0:
+ resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
+ engines: {node: '>=8'}
+
+ cli-spinners@2.9.2:
+ resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
+ engines: {node: '>=6'}
+
+ cli-width@3.0.0:
+ resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
+ engines: {node: '>= 10'}
+
+ client-only@0.0.1:
+ resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
+ clone@1.0.4:
+ resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==}
+ engines: {node: '>=0.8'}
+
+ co@4.6.0:
+ resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
+ engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
+
+ collect-v8-coverage@1.0.3:
+ resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==}
+
+ color-convert@1.9.3:
+ resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.3:
+ resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ colord@2.9.3:
+ resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
+
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
+ commander@10.0.1:
+ resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
+ engines: {node: '>=14'}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ constant-case@2.0.0:
+ resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==}
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
+ core-js-pure@3.47.0:
+ resolution: {integrity: sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==}
+
+ cosmiconfig@9.0.0:
+ resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ typescript: '>=4.9.5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ create-jest@29.7.0:
+ resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ hasBin: true
+
+ create-require@1.1.1:
+ resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ css-functions-list@3.2.3:
+ resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==}
+ engines: {node: '>=12 || >=16'}
+
+ css-property-sort-order-smacss@2.2.0:
+ resolution: {integrity: sha512-nXutswsivIEBOrPo/OZw2KQjFPLvtg68aovJf6Kqrm3L6FmTvvFPaeDrk83hh0+pRJGuP3PeKJwMS0E6DFipdQ==}
+
+ css-tree@3.1.0:
+ resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
+ engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
+
+ css.escape@1.5.1:
+ resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
+
+ cssesc@3.0.0:
+ resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ cssom@0.3.8:
+ resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==}
+
+ cssom@0.5.0:
+ resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==}
+
+ cssstyle@2.3.0:
+ resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==}
+ engines: {node: '>=8'}
+
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-drag@3.0.0:
+ resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==}
+ engines: {node: '>=12'}
+
+ d3-ease@3.0.1:
+ resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ d3-transition@3.0.1:
+ resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ d3-selection: 2 - 3
+
+ d3-zoom@3.0.0:
+ resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==}
+ engines: {node: '>=12'}
+
+ data-uri-to-buffer@6.0.2:
+ resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
+ engines: {node: '>= 14'}
+
+ data-urls@3.0.2:
+ resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==}
+ engines: {node: '>=12'}
+
+ data-view-buffer@1.0.2:
+ resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-length@1.0.2:
+ resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+ engines: {node: '>= 0.4'}
+
+ data-view-byte-offset@1.0.1:
+ resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+ engines: {node: '>= 0.4'}
+
+ date-fns@4.1.0:
+ resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+
+ debug@4.3.7:
+ resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ decimal.js@10.6.0:
+ resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
+ dedent@1.7.1:
+ resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==}
+ peerDependencies:
+ babel-plugin-macros: ^3.1.0
+ peerDependenciesMeta:
+ babel-plugin-macros:
+ optional: true
+
+ deep-extend@0.6.0:
+ resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
+ engines: {node: '>=4.0.0'}
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
+ defaults@1.0.4:
+ resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
+
+ define-data-property@1.1.4:
+ resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+ engines: {node: '>= 0.4'}
+
+ define-properties@1.2.1:
+ resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+ engines: {node: '>= 0.4'}
+
+ degenerator@5.0.1:
+ resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
+ engines: {node: '>= 14'}
+
+ del@5.1.0:
+ resolution: {integrity: sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==}
+ engines: {node: '>=8'}
+
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
+ dequal@2.0.3:
+ resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
+ engines: {node: '>=6'}
+
+ detect-libc@1.0.3:
+ resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
+ engines: {node: '>=0.10'}
+ hasBin: true
+
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
+ detect-newline@3.1.0:
+ resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
+ engines: {node: '>=8'}
+
+ diff-sequences@29.6.3:
+ resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ diff@4.0.2:
+ resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
+ engines: {node: '>=0.3.1'}
+
+ dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+
+ doctrine@2.1.0:
+ resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+ engines: {node: '>=0.10.0'}
+
+ dom-accessibility-api@0.5.16:
+ resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
+
+ dom-accessibility-api@0.6.3:
+ resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
+
+ domexception@4.0.0:
+ resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==}
+ engines: {node: '>=12'}
+ deprecated: Use your platform's native DOMException instead
+
+ dot-case@2.1.1:
+ resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==}
+
+ dotenv@16.0.3:
+ resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
+ engines: {node: '>=12'}
+
+ dunder-proto@1.0.1:
+ resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+ engines: {node: '>= 0.4'}
+
+ ejs@3.1.10:
+ resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==}
+ engines: {node: '>=0.10.0'}
+ hasBin: true
+
+ electron-to-chromium@1.5.267:
+ resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
+
+ emittery@0.13.1:
+ resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
+ engines: {node: '>=12'}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+ engine.io-client@6.6.4:
+ resolution: {integrity: sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==}
+
+ engine.io-parser@5.2.3:
+ resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
+ engines: {node: '>=10.0.0'}
+
+ entities@6.0.1:
+ resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
+ engines: {node: '>=0.12'}
+
+ env-paths@2.2.1:
+ resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
+ engines: {node: '>=6'}
+
+ error-ex@1.3.4:
+ resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+
+ es-abstract@1.24.1:
+ resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
+ engines: {node: '>= 0.4'}
+
+ es-define-property@1.0.1:
+ resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+ engines: {node: '>= 0.4'}
+
+ es-errors@1.3.0:
+ resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+ engines: {node: '>= 0.4'}
+
+ es-iterator-helpers@1.2.2:
+ resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==}
+ engines: {node: '>= 0.4'}
+
+ es-object-atoms@1.1.1:
+ resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+ engines: {node: '>= 0.4'}
+
+ es-set-tostringtag@2.1.0:
+ resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+ engines: {node: '>= 0.4'}
+
+ es-shim-unscopables@1.1.0:
+ resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+ engines: {node: '>= 0.4'}
+
+ es-to-primitive@1.3.0:
+ resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+ engines: {node: '>= 0.4'}
+
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
+ escape-string-regexp@1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+
+ escape-string-regexp@2.0.0:
+ resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
+ engines: {node: '>=8'}
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ escodegen@2.1.0:
+ resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
+ engines: {node: '>=6.0'}
+ hasBin: true
+
+ eslint-config-prettier@10.1.8:
+ resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
+ hasBin: true
+ peerDependencies:
+ eslint: '>=7.0.0'
+
+ eslint-plugin-only-warn@1.1.0:
+ resolution: {integrity: sha512-2tktqUAT+Q3hCAU0iSf4xAN1k9zOpjK5WO8104mB0rT/dGhOa09582HN5HlbxNbPRZ0THV7nLGvzugcNOSjzfA==}
+ engines: {node: '>=6'}
+
+ eslint-plugin-react-hooks@7.0.1:
+ resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
+
+ eslint-plugin-react@7.37.5:
+ resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+
+ eslint-plugin-simple-import-sort@12.1.1:
+ resolution: {integrity: sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==}
+ peerDependencies:
+ eslint: '>=5.0.0'
+
+ eslint-plugin-turbo@2.7.3:
+ resolution: {integrity: sha512-q7kYzJCyvceSLVwHgmn3ZBhqpUihQHxC7LEddq5a1eLe5P+/Ob4TnJrdocP38qO1n9MCuO+cJSUTGUtZb1X3bQ==}
+ peerDependencies:
+ eslint: '>6.6.0'
+ turbo: '>2.0.0'
+
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint@9.39.2:
+ resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esprima@4.0.1:
+ resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ esquery@1.7.0:
+ resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ execa@5.1.1:
+ resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+ engines: {node: '>=10'}
+
+ exit@0.1.2:
+ resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
+ engines: {node: '>= 0.8.0'}
+
+ expect@29.7.0:
+ resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ external-editor@3.1.0:
+ resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
+ engines: {node: '>=4'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-glob@3.3.1:
+ resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-glob@3.3.3:
+ resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+ engines: {node: '>=8.6.0'}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fast-uri@3.1.0:
+ resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+
+ fastest-levenshtein@1.0.16:
+ resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==}
+ engines: {node: '>= 4.9.1'}
+
+ fastq@1.20.1:
+ resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+
+ fb-watchman@2.0.2:
+ resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ figures@3.2.0:
+ resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
+ engines: {node: '>=8'}
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ file-entry-cache@9.1.0:
+ resolution: {integrity: sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==}
+ engines: {node: '>=18'}
+
+ filelist@1.0.4:
+ resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
+
+ fill-range@7.1.1:
+ resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+ engines: {node: '>=8'}
+
+ find-up@4.1.0:
+ resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+ engines: {node: '>=8'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flat-cache@5.0.0:
+ resolution: {integrity: sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==}
+ engines: {node: '>=18'}
+
+ flatted@3.3.3:
+ resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+
+ for-each@0.3.5:
+ resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+ engines: {node: '>= 0.4'}
+
+ form-data@4.0.5:
+ resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
+ engines: {node: '>= 6'}
+
+ fs-extra@10.1.0:
+ resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
+ engines: {node: '>=12'}
+
+ fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+ function.prototype.name@1.1.8:
+ resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
+ engines: {node: '>= 0.4'}
+
+ functions-have-names@1.2.3:
+ resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+ generator-function@2.0.1:
+ resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
+ engines: {node: '>= 0.4'}
+
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-intrinsic@1.3.0:
+ resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+ engines: {node: '>= 0.4'}
+
+ get-package-type@0.1.0:
+ resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
+ engines: {node: '>=8.0.0'}
+
+ get-proto@1.0.1:
+ resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+ engines: {node: '>= 0.4'}
+
+ get-stream@6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+
+ get-symbol-description@1.1.0:
+ resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+ engines: {node: '>= 0.4'}
+
+ get-uri@6.0.5:
+ resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==}
+ engines: {node: '>= 14'}
+
+ glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ deprecated: Glob versions prior to v9 are no longer supported
+
+ global-modules@2.0.0:
+ resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==}
+ engines: {node: '>=6'}
+
+ global-prefix@3.0.0:
+ resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==}
+ engines: {node: '>=6'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ globals@15.15.0:
+ resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
+ engines: {node: '>=18'}
+
+ globalthis@1.0.4:
+ resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+ engines: {node: '>= 0.4'}
+
+ globby@10.0.2:
+ resolution: {integrity: sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==}
+ engines: {node: '>=8'}
+
+ globby@11.1.0:
+ resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+ engines: {node: '>=10'}
+
+ globjoin@0.1.4:
+ resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
+
+ gopd@1.2.0:
+ resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+ engines: {node: '>= 0.4'}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ gradient-string@2.0.2:
+ resolution: {integrity: sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==}
+ engines: {node: '>=10'}
+
+ hammerjs@2.0.8:
+ resolution: {integrity: sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ==}
+ engines: {node: '>=0.8.0'}
+
+ handlebars@4.7.8:
+ resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==}
+ engines: {node: '>=0.4.7'}
+ hasBin: true
+
+ harmony-reflect@1.6.2:
+ resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==}
+
+ has-bigints@1.1.0:
+ resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+ engines: {node: '>= 0.4'}
+
+ has-flag@3.0.0:
+ resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+ engines: {node: '>=4'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ has-property-descriptors@1.0.2:
+ resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+ has-proto@1.2.0:
+ resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+ engines: {node: '>= 0.4'}
+
+ has-symbols@1.1.0:
+ resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+ engines: {node: '>= 0.4'}
+
+ has-tostringtag@1.0.2:
+ resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+ engines: {node: '>= 0.4'}
+
+ hasown@2.0.2:
+ resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+ engines: {node: '>= 0.4'}
+
+ header-case@1.0.1:
+ resolution: {integrity: sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==}
+
+ hermes-estree@0.25.1:
+ resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
+
+ hermes-parser@0.25.1:
+ resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+
+ hoist-non-react-statics@3.3.2:
+ resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
+
+ html-encoding-sniffer@3.0.0:
+ resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==}
+ engines: {node: '>=12'}
+
+ html-escaper@2.0.2:
+ resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+
+ html-parse-stringify@3.0.1:
+ resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==}
+
+ html-tags@3.3.1:
+ resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
+ engines: {node: '>=8'}
+
+ http-proxy-agent@5.0.0:
+ resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
+ engines: {node: '>= 6'}
+
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ https-proxy-agent@5.0.1:
+ resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
+ engines: {node: '>= 6'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ human-signals@2.1.0:
+ resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+ engines: {node: '>=10.17.0'}
+
+ i18next-browser-languagedetector@8.0.2:
+ resolution: {integrity: sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g==}
+
+ i18next-resources-to-backend@1.2.1:
+ resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==}
+
+ i18next@24.2.0:
+ resolution: {integrity: sha512-ArJJTS1lV6lgKH7yEf4EpgNZ7+THl7bsGxxougPYiXRTJ/Fe1j08/TBpV9QsXCIYVfdE/HWG/xLezJ5DOlfBOA==}
+ peerDependencies:
+ typescript: ^5
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+
+ iconv-lite@0.4.24:
+ resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+ engines: {node: '>=0.10.0'}
+
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
+ iconv-lite@0.7.2:
+ resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
+ engines: {node: '>=0.10.0'}
+
+ identity-obj-proxy@3.0.0:
+ resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==}
+ engines: {node: '>=4'}
+
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ ignore@6.0.2:
+ resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==}
+ engines: {node: '>= 4'}
+
+ ignore@7.0.5:
+ resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
+ engines: {node: '>= 4'}
+
+ immer@10.2.0:
+ resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==}
+
+ immutable@5.1.4:
+ resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ import-local@3.2.0:
+ resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
+ engines: {node: '>=8'}
+ hasBin: true
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ indent-string@4.0.0:
+ resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
+ engines: {node: '>=8'}
+
+ inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ ini@1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
+ inquirer@7.3.3:
+ resolution: {integrity: sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==}
+ engines: {node: '>=8.0.0'}
+
+ inquirer@8.2.7:
+ resolution: {integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==}
+ engines: {node: '>=12.0.0'}
+
+ internal-slot@1.1.0:
+ resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+ engines: {node: '>= 0.4'}
+
+ ip-address@10.1.0:
+ resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
+ engines: {node: '>= 12'}
+
+ is-array-buffer@3.0.5:
+ resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+ engines: {node: '>= 0.4'}
+
+ is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+ is-async-function@2.1.1:
+ resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+ engines: {node: '>= 0.4'}
+
+ is-bigint@1.1.0:
+ resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+ engines: {node: '>= 0.4'}
+
+ is-boolean-object@1.2.2:
+ resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+ engines: {node: '>= 0.4'}
+
+ is-callable@1.2.7:
+ resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+ engines: {node: '>= 0.4'}
+
+ is-core-module@2.16.1:
+ resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+ engines: {node: '>= 0.4'}
+
+ is-data-view@1.0.2:
+ resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+ engines: {node: '>= 0.4'}
+
+ is-date-object@1.1.0:
+ resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+ engines: {node: '>= 0.4'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-finalizationregistry@1.1.1:
+ resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+ engines: {node: '>= 0.4'}
+
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
+ is-generator-fn@2.1.0:
+ resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==}
+ engines: {node: '>=6'}
+
+ is-generator-function@1.1.2:
+ resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
+ engines: {node: '>= 0.4'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ is-interactive@1.0.0:
+ resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
+ engines: {node: '>=8'}
+
+ is-lower-case@1.1.3:
+ resolution: {integrity: sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==}
+
+ is-map@2.0.3:
+ resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+ engines: {node: '>= 0.4'}
+
+ is-negative-zero@2.0.3:
+ resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+ engines: {node: '>= 0.4'}
+
+ is-number-object@1.1.1:
+ resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+ engines: {node: '>= 0.4'}
+
+ is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+
+ is-path-cwd@2.2.0:
+ resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==}
+ engines: {node: '>=6'}
+
+ is-path-inside@3.0.3:
+ resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+ engines: {node: '>=8'}
+
+ is-plain-object@5.0.0:
+ resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
+ engines: {node: '>=0.10.0'}
+
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
+ is-regex@1.2.1:
+ resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+ engines: {node: '>= 0.4'}
+
+ is-set@2.0.3:
+ resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+ engines: {node: '>= 0.4'}
+
+ is-shared-array-buffer@1.0.4:
+ resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+ engines: {node: '>= 0.4'}
+
+ is-stream@2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+
+ is-string@1.1.1:
+ resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+ engines: {node: '>= 0.4'}
+
+ is-symbol@1.1.1:
+ resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+ engines: {node: '>= 0.4'}
+
+ is-typed-array@1.1.15:
+ resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+ engines: {node: '>= 0.4'}
+
+ is-unicode-supported@0.1.0:
+ resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
+ engines: {node: '>=10'}
+
+ is-upper-case@1.1.2:
+ resolution: {integrity: sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==}
+
+ is-weakmap@2.0.2:
+ resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+ engines: {node: '>= 0.4'}
+
+ is-weakref@1.1.1:
+ resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+ engines: {node: '>= 0.4'}
+
+ is-weakset@2.0.4:
+ resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+ engines: {node: '>= 0.4'}
+
+ isarray@2.0.5:
+ resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+ isbinaryfile@4.0.10:
+ resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==}
+ engines: {node: '>= 8.0.0'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ istanbul-lib-coverage@3.2.2:
+ resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
+ engines: {node: '>=8'}
+
+ istanbul-lib-instrument@5.2.1:
+ resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==}
+ engines: {node: '>=8'}
+
+ istanbul-lib-instrument@6.0.3:
+ resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==}
+ engines: {node: '>=10'}
+
+ istanbul-lib-report@3.0.1:
+ resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
+ engines: {node: '>=10'}
+
+ istanbul-lib-source-maps@4.0.1:
+ resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
+ engines: {node: '>=10'}
+
+ istanbul-reports@3.2.0:
+ resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
+ engines: {node: '>=8'}
+
+ iterator.prototype@1.1.5:
+ resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+ engines: {node: '>= 0.4'}
+
+ jake@10.9.4:
+ resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ jest-changed-files@29.7.0:
+ resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-circus@29.7.0:
+ resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-cli@29.7.0:
+ resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ jest-config@29.7.0:
+ resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ '@types/node': '*'
+ ts-node: '>=9.0.0'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ ts-node:
+ optional: true
+
+ jest-diff@29.7.0:
+ resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-docblock@29.7.0:
+ resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-each@29.7.0:
+ resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-environment-jsdom@29.7.0:
+ resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ peerDependencies:
+ canvas: ^2.5.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ jest-environment-node@29.7.0:
+ resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-get-type@29.6.3:
+ resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-haste-map@29.7.0:
+ resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-leak-detector@29.7.0:
+ resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-matcher-utils@29.7.0:
+ resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-message-util@29.7.0:
+ resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-mock@29.7.0:
+ resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-pnp-resolver@1.2.3:
+ resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==}
+ engines: {node: '>=6'}
+ peerDependencies:
+ jest-resolve: '*'
+ peerDependenciesMeta:
+ jest-resolve:
+ optional: true
+
+ jest-regex-util@29.6.3:
+ resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-resolve-dependencies@29.7.0:
+ resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-resolve@29.7.0:
+ resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-runner@29.7.0:
+ resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-runtime@29.7.0:
+ resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-snapshot@29.7.0:
+ resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-util@29.7.0:
+ resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-validate@29.7.0:
+ resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-watcher@29.7.0:
+ resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest-worker@29.7.0:
+ resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ jest@29.7.0:
+ resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+ hasBin: true
+ peerDependencies:
+ node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+ peerDependenciesMeta:
+ node-notifier:
+ optional: true
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@3.14.2:
+ resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
+ hasBin: true
+
+ js-yaml@4.1.1:
+ resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+ hasBin: true
+
+ jsdom@20.0.3:
+ resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==}
+ engines: {node: '>=14'}
+ peerDependencies:
+ canvas: ^2.5.0
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ jsonfile@6.2.0:
+ resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==}
+
+ jsx-ast-utils@3.3.5:
+ resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+ engines: {node: '>=4.0'}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ kind-of@6.0.3:
+ resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
+ engines: {node: '>=0.10.0'}
+
+ kleur@3.0.3:
+ resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
+ engines: {node: '>=6'}
+
+ known-css-properties@0.34.0:
+ resolution: {integrity: sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==}
+
+ known-css-properties@0.37.0:
+ resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==}
+
+ leven@3.1.0:
+ resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
+ engines: {node: '>=6'}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ locate-path@5.0.0:
+ resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+ engines: {node: '>=8'}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.debounce@4.0.8:
+ resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
+
+ lodash.get@4.4.2:
+ resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
+ deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
+
+ lodash.isplainobject@4.0.6:
+ resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
+
+ lodash.memoize@4.1.2:
+ resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ lodash.truncate@4.4.2:
+ resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==}
+
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ log-symbols@3.0.0:
+ resolution: {integrity: sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==}
+ engines: {node: '>=8'}
+
+ log-symbols@4.1.0:
+ resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
+ engines: {node: '>=10'}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
+ lower-case-first@1.0.2:
+ resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==}
+
+ lower-case@1.1.4:
+ resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==}
+
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ lru-cache@7.18.3:
+ resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
+ engines: {node: '>=12'}
+
+ luxon@3.5.0:
+ resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
+ engines: {node: '>=12'}
+
+ lz-string@1.5.0:
+ resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
+ hasBin: true
+
+ make-dir@4.0.0:
+ resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
+ engines: {node: '>=10'}
+
+ make-error@1.3.6:
+ resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+ makeerror@1.0.12:
+ resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
+
+ math-intrinsics@1.1.0:
+ resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+ engines: {node: '>= 0.4'}
+
+ mathml-tag-names@2.1.3:
+ resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
+
+ mdn-data@2.12.2:
+ resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
+
+ mdn-data@2.26.0:
+ resolution: {integrity: sha512-ZqI0qjKWHMPcGUfLmlr80NPNVHIOjPMHtIOe1qXYFGS0YBZ1YKAzo9yk8W+gGrLCN0Xdv/RKxqdIsqPakEfmow==}
+
+ meow@13.2.0:
+ resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==}
+ engines: {node: '>=18'}
+
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+ merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+
+ micromatch@4.0.8:
+ resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+ engines: {node: '>=8.6'}
+
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mimic-fn@2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+
+ min-indent@1.0.1:
+ resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
+ engines: {node: '>=4'}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ minimatch@5.1.6:
+ resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
+ engines: {node: '>=10'}
+
+ minimatch@9.0.5:
+ resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
+ mkdirp@0.5.6:
+ resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
+ hasBin: true
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ mute-stream@0.0.8:
+ resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
+ neo-async@2.6.2:
+ resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
+
+ netmask@2.0.2:
+ resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
+ engines: {node: '>= 0.4.0'}
+
+ next-redux-wrapper@8.1.0:
+ resolution: {integrity: sha512-2hIau0hcI6uQszOtrvAFqgc0NkZegKYhBB7ZAKiG3jk7zfuQb4E7OV9jfxViqqojh3SEHdnFfPkN9KErttUKuw==}
+ peerDependencies:
+ next: '>=9'
+ react: '*'
+ react-redux: '*'
+
+ next@15.5.7:
+ resolution: {integrity: sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ==}
+ engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
+ deprecated: This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.51.1
+ babel-plugin-react-compiler: '*'
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
+
+ no-case@2.3.2:
+ resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==}
+
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
+ node-int64@0.4.0:
+ resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
+
+ node-plop@0.26.3:
+ resolution: {integrity: sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q==}
+ engines: {node: '>=8.9.4'}
+
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ npm-run-path@4.0.1:
+ resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+ engines: {node: '>=8'}
+
+ nwsapi@2.2.23:
+ resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ object-inspect@1.13.4:
+ resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+ engines: {node: '>= 0.4'}
+
+ object-keys@1.1.1:
+ resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+ engines: {node: '>= 0.4'}
+
+ object.assign@4.1.7:
+ resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+ engines: {node: '>= 0.4'}
+
+ object.entries@1.1.9:
+ resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
+ engines: {node: '>= 0.4'}
+
+ object.fromentries@2.0.8:
+ resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+ engines: {node: '>= 0.4'}
+
+ object.values@1.2.1:
+ resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
+ engines: {node: '>= 0.4'}
+
+ once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+ onetime@5.1.2:
+ resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+ engines: {node: '>=6'}
+
+ only-allow@1.2.2:
+ resolution: {integrity: sha512-uxyNYDsCh5YIJ780G7hC5OHjVUr9reHsbZNMM80L9tZlTpb3hUzb36KXgW4ZUGtJKQnGA3xegmWg1BxhWV0jJA==}
+ hasBin: true
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ ora@4.1.1:
+ resolution: {integrity: sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==}
+ engines: {node: '>=8'}
+
+ ora@5.4.1:
+ resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
+ engines: {node: '>=10'}
+
+ os-tmpdir@1.0.2:
+ resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
+ engines: {node: '>=0.10.0'}
+
+ own-keys@1.0.1:
+ resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+ engines: {node: '>= 0.4'}
+
+ p-limit@2.3.0:
+ resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+ engines: {node: '>=6'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@4.1.0:
+ resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+ engines: {node: '>=8'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ p-map@3.0.0:
+ resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==}
+ engines: {node: '>=8'}
+
+ p-try@2.2.0:
+ resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+ engines: {node: '>=6'}
+
+ pac-proxy-agent@7.2.0:
+ resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
+ engines: {node: '>= 14'}
+
+ pac-resolver@7.0.1:
+ resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
+ engines: {node: '>= 14'}
+
+ param-case@2.1.1:
+ resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+
+ parse5@7.3.0:
+ resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
+
+ pascal-case@2.0.1:
+ resolution: {integrity: sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==}
+
+ path-case@2.1.1:
+ resolution: {integrity: sha512-Ou0N05MioItesaLr9q8TtHVWmJ6fxWdqKB2RohFmNWVyJ+2zeKIeDNWAN6B/Pe7wpzWChhZX6nONYmOnMeJQ/Q==}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+ path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ pirates@4.0.7:
+ resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+ engines: {node: '>= 6'}
+
+ pkg-dir@4.2.0:
+ resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
+ engines: {node: '>=8'}
+
+ possible-typed-array-names@1.1.0:
+ resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+ engines: {node: '>= 0.4'}
+
+ postcss-media-query-parser@0.2.3:
+ resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==}
+
+ postcss-resolve-nested-selector@0.1.6:
+ resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==}
+
+ postcss-safe-parser@7.0.1:
+ resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==}
+ engines: {node: '>=18.0'}
+ peerDependencies:
+ postcss: ^8.4.31
+
+ postcss-scss@4.0.9:
+ resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==}
+ engines: {node: '>=12.0'}
+ peerDependencies:
+ postcss: ^8.4.29
+
+ postcss-selector-parser@6.1.2:
+ resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
+ engines: {node: '>=4'}
+
+ postcss-selector-parser@7.1.1:
+ resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
+ engines: {node: '>=4'}
+
+ postcss-sorting@8.0.2:
+ resolution: {integrity: sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==}
+ peerDependencies:
+ postcss: ^8.4.20
+
+ postcss-value-parser@4.2.0:
+ resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
+
+ postcss@8.4.31:
+ resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier@3.4.2:
+ resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ pretty-format@27.5.1:
+ resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
+ engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+ pretty-format@29.7.0:
+ resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
+ engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
+
+ prompts@2.4.2:
+ resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
+ engines: {node: '>= 6'}
+
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+ proxy-agent@6.5.0:
+ resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
+ engines: {node: '>= 14'}
+
+ proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+
+ psl@1.15.0:
+ resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ pure-rand@6.1.0:
+ resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
+
+ querystringify@2.2.0:
+ resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
+
+ queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+ rc@1.2.8:
+ resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
+ hasBin: true
+
+ react-chartjs-2@5.3.0:
+ resolution: {integrity: sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==}
+ peerDependencies:
+ chart.js: ^4.1.1
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ react-cookie@7.2.2:
+ resolution: {integrity: sha512-e+hi6axHcw9VODoeVu8WyMWyoosa1pzpyjfvrLdF7CexfU+WSGZdDuRfHa4RJgTpfv3ZjdIpHE14HpYBieHFhg==}
+ peerDependencies:
+ react: '>= 16.3.0'
+
+ react-dom@19.2.3:
+ resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
+ peerDependencies:
+ react: ^19.2.3
+
+ react-i18next@15.4.0:
+ resolution: {integrity: sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==}
+ peerDependencies:
+ i18next: '>= 23.2.3'
+ react: '>= 16.8.0'
+ react-dom: '*'
+ react-native: '*'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ react-native:
+ optional: true
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-is@17.0.2:
+ resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
+ react-is@18.3.1:
+ resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+ react-redux@9.2.0:
+ resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==}
+ peerDependencies:
+ '@types/react': ^18.2.25 || ^19
+ react: ^18.0 || ^19
+ redux: ^5.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ redux:
+ optional: true
+
+ react-spinners@0.15.0:
+ resolution: {integrity: sha512-ZO3/fNB9Qc+kgpG3SfdlMnvTX6LtLmTnOogb3W6sXIaU/kZ1ydEViPfZ06kSOaEsor58C/tzXw2wROGQu3X2pA==}
+ peerDependencies:
+ react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ react@19.2.3:
+ resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
+ engines: {node: '>=0.10.0'}
+
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
+ readdirp@4.1.2:
+ resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
+ engines: {node: '>= 14.18.0'}
+
+ redent@3.0.0:
+ resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
+ engines: {node: '>=8'}
+
+ redux-mock-store@1.5.5:
+ resolution: {integrity: sha512-YxX+ofKUTQkZE4HbhYG4kKGr7oCTJfB0GLy7bSeqx86GLpGirrbUWstMnqXkqHNaQpcnbMGbof2dYs5KsPE6Zg==}
+ peerDependencies:
+ redux: '*'
+
+ redux-thunk@3.1.0:
+ resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
+ peerDependencies:
+ redux: ^5.0.0
+
+ redux@4.2.1:
+ resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
+
+ redux@5.0.1:
+ resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
+
+ reflect.getprototypeof@1.0.10:
+ resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+ engines: {node: '>= 0.4'}
+
+ regexp.prototype.flags@1.5.4:
+ resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+ engines: {node: '>= 0.4'}
+
+ registry-auth-token@3.3.2:
+ resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==}
+
+ registry-url@3.1.0:
+ resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==}
+ engines: {node: '>=0.10.0'}
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
+ require-from-string@2.0.2:
+ resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
+ engines: {node: '>=0.10.0'}
+
+ requires-port@1.0.0:
+ resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
+
+ reselect@5.1.1:
+ resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
+
+ resolve-cwd@3.0.0:
+ resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
+ engines: {node: '>=8'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ resolve-from@5.0.0:
+ resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+ engines: {node: '>=8'}
+
+ resolve.exports@2.0.3:
+ resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==}
+ engines: {node: '>=10'}
+
+ resolve@1.22.11:
+ resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
+ engines: {node: '>= 0.4'}
+ hasBin: true
+
+ resolve@2.0.0-next.5:
+ resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
+ hasBin: true
+
+ restore-cursor@3.1.0:
+ resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
+ engines: {node: '>=8'}
+
+ reusify@1.1.0:
+ resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+ rimraf@3.0.2:
+ resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
+ run-async@2.4.1:
+ resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==}
+ engines: {node: '>=0.12.0'}
+
+ run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+ rxjs@6.6.7:
+ resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==}
+ engines: {npm: '>=2.0.0'}
+
+ rxjs@7.8.2:
+ resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+
+ safe-array-concat@1.1.3:
+ resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
+ engines: {node: '>=0.4'}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-push-apply@1.0.0:
+ resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+ engines: {node: '>= 0.4'}
+
+ safe-regex-test@1.1.0:
+ resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+ engines: {node: '>= 0.4'}
+
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ sass@1.83.0:
+ resolution: {integrity: sha512-qsSxlayzoOjdvXMVLkzF84DJFc2HZEL/rFyGIKbbilYtAvlCxyuzUeff9LawTn4btVnLKg75Z8MMr1lxU1lfGw==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+
+ saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
+ scheduler@0.27.0:
+ resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
+ semver@7.7.3:
+ resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ sentence-case@2.1.1:
+ resolution: {integrity: sha512-ENl7cYHaK/Ktwk5OTD+aDbQ3uC8IByu/6Bkg+HDv8Mm+XnBnppVNalcfJTNsp1ibstKh030/JKQQWglDvtKwEQ==}
+
+ set-function-length@1.2.2:
+ resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+ engines: {node: '>= 0.4'}
+
+ set-function-name@2.0.2:
+ resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+ engines: {node: '>= 0.4'}
+
+ set-proto@1.0.0:
+ resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+ engines: {node: '>= 0.4'}
+
+ sharp@0.34.5:
+ resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
+ engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ side-channel-list@1.0.0:
+ resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-map@1.0.1:
+ resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+ engines: {node: '>= 0.4'}
+
+ side-channel-weakmap@1.0.2:
+ resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+ engines: {node: '>= 0.4'}
+
+ side-channel@1.1.0:
+ resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+ engines: {node: '>= 0.4'}
+
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ sisteransi@1.0.5:
+ resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
+ slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+
+ slice-ansi@4.0.0:
+ resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
+ engines: {node: '>=10'}
+
+ smart-buffer@4.2.0:
+ resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
+ engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
+
+ snake-case@2.1.0:
+ resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==}
+
+ socket.io-client@4.8.1:
+ resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==}
+ engines: {node: '>=10.0.0'}
+
+ socket.io-parser@4.2.5:
+ resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==}
+ engines: {node: '>=10.0.0'}
+
+ socks-proxy-agent@8.0.5:
+ resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
+ engines: {node: '>= 14'}
+
+ socks@2.8.7:
+ resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
+ engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ source-map-support@0.5.13:
+ resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}
+
+ source-map@0.6.1:
+ resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+ engines: {node: '>=0.10.0'}
+
+ sprintf-js@1.0.3:
+ resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
+ stack-utils@2.0.6:
+ resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
+ engines: {node: '>=10'}
+
+ stop-iteration-iterator@1.1.0:
+ resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+ engines: {node: '>= 0.4'}
+
+ string-length@4.0.2:
+ resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
+ engines: {node: '>=10'}
+
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string.prototype.matchall@4.0.12:
+ resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.repeat@1.0.0:
+ resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
+ string.prototype.trim@1.2.10:
+ resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimend@1.0.9:
+ resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+ engines: {node: '>= 0.4'}
+
+ string.prototype.trimstart@1.0.8:
+ resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+ engines: {node: '>= 0.4'}
+
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-bom@4.0.0:
+ resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
+ engines: {node: '>=8'}
+
+ strip-final-newline@2.0.0:
+ resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+ engines: {node: '>=6'}
+
+ strip-indent@3.0.0:
+ resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
+ engines: {node: '>=8'}
+
+ strip-json-comments@2.0.1:
+ resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+ engines: {node: '>=0.10.0'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ styled-jsx@5.1.6:
+ resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
+ engines: {node: '>= 12.0.0'}
+ peerDependencies:
+ '@babel/core': '*'
+ babel-plugin-macros: '*'
+ react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ babel-plugin-macros:
+ optional: true
+
+ stylelint-config-prettier-scss@1.0.0:
+ resolution: {integrity: sha512-Gr2qLiyvJGKeDk0E/+awNTrZB/UtNVPLqCDOr07na/sLekZwm26Br6yYIeBYz3ulsEcQgs5j+2IIMXCC+wsaQA==}
+ engines: {node: 14.* || 16.* || >= 18}
+ hasBin: true
+ peerDependencies:
+ stylelint: '>=15.0.0'
+
+ stylelint-config-property-sort-order-smacss@10.0.0:
+ resolution: {integrity: sha512-NuiTgyqD8UdYY1IpTBIodBbrWKwaib5r8sq5kGHQ52UrmT8O7Fa8ZWYGipSZw6k9tGoljl9Hng2jtH+wBTMa1Q==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ stylelint: ^14.0.0 || ^15.0.0 || ^16.0.0
+
+ stylelint-config-recommended-scss@14.1.0:
+ resolution: {integrity: sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ postcss: ^8.3.3
+ stylelint: ^16.6.1
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+
+ stylelint-config-recommended@14.0.1:
+ resolution: {integrity: sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ stylelint: ^16.1.0
+
+ stylelint-config-standard-scss@13.1.0:
+ resolution: {integrity: sha512-Eo5w7/XvwGHWkeGLtdm2FZLOMYoZl1omP2/jgFCXyl2x5yNz7/8vv4Tj6slHvMSSUNTaGoam/GAZ0ZhukvalfA==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ postcss: ^8.3.3
+ stylelint: ^16.3.1
+ peerDependenciesMeta:
+ postcss:
+ optional: true
+
+ stylelint-config-standard@36.0.1:
+ resolution: {integrity: sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ stylelint: ^16.1.0
+
+ stylelint-order@6.0.4:
+ resolution: {integrity: sha512-0UuKo4+s1hgQ/uAxlYU4h0o0HS4NiQDud0NAUNI0aa8FJdmYHA5ZZTFHiV5FpmE3071e9pZx5j0QpVJW5zOCUA==}
+ peerDependencies:
+ stylelint: ^14.0.0 || ^15.0.0 || ^16.0.1
+
+ stylelint-scss@6.14.0:
+ resolution: {integrity: sha512-ZKmHMZolxeuYsnB+PCYrTpFce0/QWX9i9gh0hPXzp73WjuIMqUpzdQaBCrKoLWh6XtCFSaNDErkMPqdjy1/8aA==}
+ engines: {node: '>=18.12.0'}
+ peerDependencies:
+ stylelint: ^16.8.2
+
+ stylelint@16.10.0:
+ resolution: {integrity: sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==}
+ engines: {node: '>=18.12.0'}
+ hasBin: true
+
+ supports-color@5.5.0:
+ resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+ engines: {node: '>=4'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
+
+ supports-hyperlinks@3.2.0:
+ resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==}
+ engines: {node: '>=14.18'}
+
+ supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+
+ svg-tags@1.0.0:
+ resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
+
+ swap-case@1.1.2:
+ resolution: {integrity: sha512-BAmWG6/bx8syfc6qXPprof3Mn5vQgf5dwdUNJhsNqU9WdPt5P+ES/wQ5bxfijy8zwZgZZHslC3iAsxsuQMCzJQ==}
+
+ swr-models@1.0.2:
+ resolution: {integrity: sha512-HSKkpjAINKmq67acDSD5BJAgWXa6qHV8QTHCmnqOXdgu5xYAtkletIS2mkFXi3FLpJnY/RNY5FXt6rjc0TxctQ==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ swr: ^2.3.0
+
+ swr@2.3.8:
+ resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==}
+ peerDependencies:
+ react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
+ table@6.9.0:
+ resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==}
+ engines: {node: '>=10.0.0'}
+
+ test-exclude@6.0.0:
+ resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
+ engines: {node: '>=8'}
+
+ through@2.3.8:
+ resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
+
+ tinycolor2@1.6.0:
+ resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
+
+ tinyglobby@0.2.15:
+ resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
+ engines: {node: '>=12.0.0'}
+
+ tinygradient@1.1.5:
+ resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==}
+
+ title-case@2.1.1:
+ resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==}
+
+ tmp@0.0.33:
+ resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==}
+ engines: {node: '>=0.6.0'}
+
+ tmpl@1.0.5:
+ resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
+
+ to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+
+ tough-cookie@4.1.4:
+ resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
+ engines: {node: '>=6'}
+
+ tr46@3.0.0:
+ resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
+ engines: {node: '>=12'}
+
+ ts-api-utils@2.4.0:
+ resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ typescript: '>=4.8.4'
+
+ ts-jest@29.2.5:
+ resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==}
+ engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0}
+ hasBin: true
+ peerDependencies:
+ '@babel/core': '>=7.0.0-beta.0 <8'
+ '@jest/transform': ^29.0.0
+ '@jest/types': ^29.0.0
+ babel-jest: ^29.0.0
+ esbuild: '*'
+ jest: ^29.0.0
+ typescript: '>=4.3 <6'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ '@jest/transform':
+ optional: true
+ '@jest/types':
+ optional: true
+ babel-jest:
+ optional: true
+ esbuild:
+ optional: true
+
+ ts-node@10.9.2:
+ resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': '>=1.2.50'
+ '@swc/wasm': '>=1.2.50'
+ '@types/node': '*'
+ typescript: '>=2.7'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ '@swc/wasm':
+ optional: true
+
+ tslib@1.14.1:
+ resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
+
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
+ turbo-darwin-64@2.5.8:
+ resolution: {integrity: sha512-Dh5bCACiHO8rUXZLpKw+m3FiHtAp2CkanSyJre+SInEvEr5kIxjGvCK/8MFX8SFRjQuhjtvpIvYYZJB4AGCxNQ==}
+ cpu: [x64]
+ os: [darwin]
+
+ turbo-darwin-arm64@2.5.8:
+ resolution: {integrity: sha512-f1H/tQC9px7+hmXn6Kx/w8Jd/FneIUnvLlcI/7RGHunxfOkKJKvsoiNzySkoHQ8uq1pJnhJ0xNGTlYM48ZaJOQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ turbo-linux-64@2.5.8:
+ resolution: {integrity: sha512-hMyvc7w7yadBlZBGl/bnR6O+dJTx3XkTeyTTH4zEjERO6ChEs0SrN8jTFj1lueNXKIHh1SnALmy6VctKMGnWfw==}
+ cpu: [x64]
+ os: [linux]
+
+ turbo-linux-arm64@2.5.8:
+ resolution: {integrity: sha512-LQELGa7bAqV2f+3rTMRPnj5G/OHAe2U+0N9BwsZvfMvHSUbsQ3bBMWdSQaYNicok7wOZcHjz2TkESn1hYK6xIQ==}
+ cpu: [arm64]
+ os: [linux]
+
+ turbo-windows-64@2.5.8:
+ resolution: {integrity: sha512-3YdcaW34TrN1AWwqgYL9gUqmZsMT4T7g8Y5Azz+uwwEJW+4sgcJkIi9pYFyU4ZBSjBvkfuPZkGgfStir5BBDJQ==}
+ cpu: [x64]
+ os: [win32]
+
+ turbo-windows-arm64@2.5.8:
+ resolution: {integrity: sha512-eFC5XzLmgXJfnAK3UMTmVECCwuBcORrWdewoiXBnUm934DY6QN8YowC/srhNnROMpaKaqNeRpoB5FxCww3eteQ==}
+ cpu: [arm64]
+ os: [win32]
+
+ turbo@2.5.8:
+ resolution: {integrity: sha512-5c9Fdsr9qfpT3hA0EyYSFRZj1dVVsb6KIWubA9JBYZ/9ZEAijgUEae0BBR/Xl/wekt4w65/lYLTFaP3JmwSO8w==}
+ hasBin: true
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ type-detect@4.0.8:
+ resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
+ engines: {node: '>=4'}
+
+ type-fest@0.21.3:
+ resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
+ engines: {node: '>=10'}
+
+ typed-array-buffer@1.0.3:
+ resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-length@1.0.3:
+ resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-byte-offset@1.0.4:
+ resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+ engines: {node: '>= 0.4'}
+
+ typed-array-length@1.0.7:
+ resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
+ engines: {node: '>= 0.4'}
+
+ typescript-eslint@8.52.0:
+ resolution: {integrity: sha512-atlQQJ2YkO4pfTVQmQ+wvYQwexPDOIgo+RaVcD7gHgzy/IQA+XTyuxNM9M9TVXvttkF7koBHmcwisKdOAf2EcA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ peerDependencies:
+ eslint: ^8.57.0 || ^9.0.0
+ typescript: '>=4.8.4 <6.0.0'
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ uglify-js@3.19.3:
+ resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==}
+ engines: {node: '>=0.8.0'}
+ hasBin: true
+
+ unbox-primitive@1.1.0:
+ resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+ engines: {node: '>= 0.4'}
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+ universal-cookie@7.2.2:
+ resolution: {integrity: sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==}
+
+ universalify@0.2.0:
+ resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
+ engines: {node: '>= 4.0.0'}
+
+ universalify@2.0.1:
+ resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
+ engines: {node: '>= 10.0.0'}
+
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
+ update-check@1.5.4:
+ resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==}
+
+ upper-case-first@1.1.2:
+ resolution: {integrity: sha512-wINKYvI3Db8dtjikdAqoBbZoP6Q+PZUyfMR7pmwHzjC2quzSkUq5DmPrTtPEqHaz8AGtmsB4TqwapMTM1QAQOQ==}
+
+ upper-case@1.1.3:
+ resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==}
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ url-parse@1.5.10:
+ resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
+
+ use-sync-external-store@1.6.0:
+ resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
+ v8-compile-cache-lib@3.0.1:
+ resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
+
+ v8-to-istanbul@9.3.0:
+ resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
+ engines: {node: '>=10.12.0'}
+
+ validate-npm-package-name@5.0.1:
+ resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ void-elements@3.1.0:
+ resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
+ engines: {node: '>=0.10.0'}
+
+ w3c-xmlserializer@4.0.0:
+ resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==}
+ engines: {node: '>=14'}
+
+ walker@1.0.8:
+ resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
+
+ wcwidth@1.0.1:
+ resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==}
+
+ webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
+ whatwg-encoding@2.0.0:
+ resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
+ engines: {node: '>=12'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
+
+ whatwg-mimetype@3.0.0:
+ resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==}
+ engines: {node: '>=12'}
+
+ whatwg-url@11.0.0:
+ resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==}
+ engines: {node: '>=12'}
+
+ which-boxed-primitive@1.1.1:
+ resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+ engines: {node: '>= 0.4'}
+
+ which-builtin-type@1.2.1:
+ resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+ engines: {node: '>= 0.4'}
+
+ which-collection@1.0.2:
+ resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+ engines: {node: '>= 0.4'}
+
+ which-pm-runs@1.1.0:
+ resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
+ engines: {node: '>=4'}
+
+ which-typed-array@1.1.19:
+ resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
+ engines: {node: '>= 0.4'}
+
+ which@1.3.1:
+ resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
+ hasBin: true
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ wordwrap@1.0.0:
+ resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
+
+ wrap-ansi@6.2.0:
+ resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
+ engines: {node: '>=8'}
+
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+ write-file-atomic@4.0.2:
+ resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
+ engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+
+ write-file-atomic@5.0.1:
+ resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ ws@8.18.3:
+ resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ ws@8.19.0:
+ resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xml-name-validator@4.0.0:
+ resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
+ engines: {node: '>=12'}
+
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
+ xmlhttprequest-ssl@2.1.2:
+ resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
+ engines: {node: '>=0.4.0'}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
+ yn@3.1.1:
+ resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+ engines: {node: '>=6'}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+ zod-validation-error@4.0.2:
+ resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ zod: ^3.25.0 || ^4.0.0
+
+ zod@4.3.5:
+ resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==}
+
+ zustand@4.5.7:
+ resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==}
+ engines: {node: '>=12.7.0'}
+ peerDependencies:
+ '@types/react': '>=16.8'
+ immer: '>=9.0.6'
+ react: '>=16.8'
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ immer:
+ optional: true
+ react:
+ optional: true
+
+snapshots:
+
+ '@adobe/css-tools@4.4.4': {}
+
+ '@babel/code-frame@7.27.1':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.5': {}
+
+ '@babel/core@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-compilation-targets': 7.27.2
+ '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5)
+ '@babel/helpers': 7.28.4
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.28.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.27.2':
+ dependencies:
+ '@babel/compat-data': 7.28.5
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.27.1':
+ dependencies:
+ '@babel/traverse': 7.28.5
+ '@babel/types': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-module-imports': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.27.1': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.4':
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+
+ '@babel/parser@7.28.5':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/helper-plugin-utils': 7.27.1
+
+ '@babel/runtime-corejs3@7.28.4':
+ dependencies:
+ core-js-pure: 3.47.0
+
+ '@babel/runtime@7.28.4': {}
+
+ '@babel/template@7.27.2':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+
+ '@babel/traverse@7.28.5':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/generator': 7.28.5
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.5
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.28.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
+ '@bcoe/v8-coverage@0.2.3': {}
+
+ '@biomejs/biome@2.3.8':
+ optionalDependencies:
+ '@biomejs/cli-darwin-arm64': 2.3.8
+ '@biomejs/cli-darwin-x64': 2.3.8
+ '@biomejs/cli-linux-arm64': 2.3.8
+ '@biomejs/cli-linux-arm64-musl': 2.3.8
+ '@biomejs/cli-linux-x64': 2.3.8
+ '@biomejs/cli-linux-x64-musl': 2.3.8
+ '@biomejs/cli-win32-arm64': 2.3.8
+ '@biomejs/cli-win32-x64': 2.3.8
+
+ '@biomejs/cli-darwin-arm64@2.3.8':
+ optional: true
+
+ '@biomejs/cli-darwin-x64@2.3.8':
+ optional: true
+
+ '@biomejs/cli-linux-arm64-musl@2.3.8':
+ optional: true
+
+ '@biomejs/cli-linux-arm64@2.3.8':
+ optional: true
+
+ '@biomejs/cli-linux-x64-musl@2.3.8':
+ optional: true
+
+ '@biomejs/cli-linux-x64@2.3.8':
+ optional: true
+
+ '@biomejs/cli-win32-arm64@2.3.8':
+ optional: true
+
+ '@biomejs/cli-win32-x64@2.3.8':
+ optional: true
+
+ '@cspotcode/source-map-support@0.8.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.9
+
+ '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-tokenizer@3.0.4': {}
+
+ '@csstools/media-query-list-parser@3.0.1(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/selector-specificity@4.0.0(postcss-selector-parser@6.1.2)':
+ dependencies:
+ postcss-selector-parser: 6.1.2
+
+ '@dnd-kit/accessibility@3.1.1(react@19.2.3)':
+ dependencies:
+ react: 19.2.3
+ tslib: 2.8.1
+
+ '@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ dependencies:
+ '@dnd-kit/accessibility': 3.1.1(react@19.2.3)
+ '@dnd-kit/utilities': 3.2.2(react@19.2.3)
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ tslib: 2.8.1
+
+ '@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
+ dependencies:
+ '@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@dnd-kit/utilities': 3.2.2(react@19.2.3)
+ react: 19.2.3
+ tslib: 2.8.1
+
+ '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
+ dependencies:
+ '@dnd-kit/core': 6.3.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ '@dnd-kit/utilities': 3.2.2(react@19.2.3)
+ react: 19.2.3
+ tslib: 2.8.1
+
+ '@dnd-kit/utilities@3.2.2(react@19.2.3)':
+ dependencies:
+ react: 19.2.3
+ tslib: 2.8.1
+
+ '@dual-bundle/import-meta-resolve@4.2.1': {}
+
+ '@emnapi/runtime@1.8.1':
+ dependencies:
+ tslib: 2.8.1
+ optional: true
+
+ '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)':
+ dependencies:
+ eslint: 9.39.2
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.2': {}
+
+ '@eslint/config-array@0.21.1':
+ dependencies:
+ '@eslint/object-schema': 2.1.7
+ debug: 4.4.3
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.4.2':
+ dependencies:
+ '@eslint/core': 0.17.0
+
+ '@eslint/core@0.17.0':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.3':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.4.3
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.1
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.39.2': {}
+
+ '@eslint/object-schema@2.1.7': {}
+
+ '@eslint/plugin-kit@0.4.1':
+ dependencies:
+ '@eslint/core': 0.17.0
+ levn: 0.4.1
+
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.7':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.4.3
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@img/colour@1.0.0':
+ optional: true
+
+ '@img/sharp-darwin-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-darwin-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-libvips-darwin-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-darwin-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-arm@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-ppc64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-riscv64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-s390x@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linux-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-arm64@1.2.4':
+ optional: true
+
+ '@img/sharp-libvips-linuxmusl-x64@1.2.4':
+ optional: true
+
+ '@img/sharp-linux-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-arm@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-ppc64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-riscv64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-s390x@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ optional: true
+
+ '@img/sharp-linux-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-arm64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ optional: true
+
+ '@img/sharp-linuxmusl-x64@0.34.5':
+ optionalDependencies:
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ optional: true
+
+ '@img/sharp-wasm32@0.34.5':
+ dependencies:
+ '@emnapi/runtime': 1.8.1
+ optional: true
+
+ '@img/sharp-win32-arm64@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-ia32@0.34.5':
+ optional: true
+
+ '@img/sharp-win32-x64@0.34.5':
+ optional: true
+
+ '@inquirer/external-editor@1.0.3(@types/node@22.19.5)':
+ dependencies:
+ chardet: 2.1.1
+ iconv-lite: 0.7.2
+ optionalDependencies:
+ '@types/node': 22.19.5
+
+ '@istanbuljs/load-nyc-config@1.1.0':
+ dependencies:
+ camelcase: 5.3.1
+ find-up: 4.1.0
+ get-package-type: 0.1.0
+ js-yaml: 3.14.2
+ resolve-from: 5.0.0
+
+ '@istanbuljs/schema@0.1.3': {}
+
+ '@jest/console@29.7.0':
+ dependencies:
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ chalk: 4.1.2
+ jest-message-util: 29.7.0
+ jest-util: 29.7.0
+ slash: 3.0.0
+
+ '@jest/core@29.7.0(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))':
+ dependencies:
+ '@jest/console': 29.7.0
+ '@jest/reporters': 29.7.0
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ exit: 0.1.2
+ graceful-fs: 4.2.11
+ jest-changed-files: 29.7.0
+ jest-config: 29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ jest-haste-map: 29.7.0
+ jest-message-util: 29.7.0
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-resolve-dependencies: 29.7.0
+ jest-runner: 29.7.0
+ jest-runtime: 29.7.0
+ jest-snapshot: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ jest-watcher: 29.7.0
+ micromatch: 4.0.8
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-ansi: 6.0.1
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ '@jest/environment@29.7.0':
+ dependencies:
+ '@jest/fake-timers': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ jest-mock: 29.7.0
+
+ '@jest/expect-utils@29.7.0':
+ dependencies:
+ jest-get-type: 29.6.3
+
+ '@jest/expect@29.7.0':
+ dependencies:
+ expect: 29.7.0
+ jest-snapshot: 29.7.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/fake-timers@29.7.0':
+ dependencies:
+ '@jest/types': 29.6.3
+ '@sinonjs/fake-timers': 10.3.0
+ '@types/node': 22.19.5
+ jest-message-util: 29.7.0
+ jest-mock: 29.7.0
+ jest-util: 29.7.0
+
+ '@jest/globals@29.7.0':
+ dependencies:
+ '@jest/environment': 29.7.0
+ '@jest/expect': 29.7.0
+ '@jest/types': 29.6.3
+ jest-mock: 29.7.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/reporters@29.7.0':
+ dependencies:
+ '@bcoe/v8-coverage': 0.2.3
+ '@jest/console': 29.7.0
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0
+ '@jest/types': 29.6.3
+ '@jridgewell/trace-mapping': 0.3.31
+ '@types/node': 22.19.5
+ chalk: 4.1.2
+ collect-v8-coverage: 1.0.3
+ exit: 0.1.2
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-instrument: 6.0.3
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 4.0.1
+ istanbul-reports: 3.2.0
+ jest-message-util: 29.7.0
+ jest-util: 29.7.0
+ jest-worker: 29.7.0
+ slash: 3.0.0
+ string-length: 4.0.2
+ strip-ansi: 6.0.1
+ v8-to-istanbul: 9.3.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/schemas@29.6.3':
+ dependencies:
+ '@sinclair/typebox': 0.27.8
+
+ '@jest/source-map@29.6.3':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ callsites: 3.1.0
+ graceful-fs: 4.2.11
+
+ '@jest/test-result@29.7.0':
+ dependencies:
+ '@jest/console': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/istanbul-lib-coverage': 2.0.6
+ collect-v8-coverage: 1.0.3
+
+ '@jest/test-sequencer@29.7.0':
+ dependencies:
+ '@jest/test-result': 29.7.0
+ graceful-fs: 4.2.11
+ jest-haste-map: 29.7.0
+ slash: 3.0.0
+
+ '@jest/transform@29.7.0':
+ dependencies:
+ '@babel/core': 7.28.5
+ '@jest/types': 29.6.3
+ '@jridgewell/trace-mapping': 0.3.31
+ babel-plugin-istanbul: 6.1.1
+ chalk: 4.1.2
+ convert-source-map: 2.0.0
+ fast-json-stable-stringify: 2.1.0
+ graceful-fs: 4.2.11
+ jest-haste-map: 29.7.0
+ jest-regex-util: 29.6.3
+ jest-util: 29.7.0
+ micromatch: 4.0.8
+ pirates: 4.0.7
+ slash: 3.0.0
+ write-file-atomic: 4.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@jest/types@29.6.3':
+ dependencies:
+ '@jest/schemas': 29.6.3
+ '@types/istanbul-lib-coverage': 2.0.6
+ '@types/istanbul-reports': 3.0.4
+ '@types/node': 22.19.5
+ '@types/yargs': 17.0.35
+ chalk: 4.1.2
+
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/sourcemap-codec@1.5.5': {}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@jridgewell/trace-mapping@0.3.9':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
+ '@kurkle/color@0.3.4': {}
+
+ '@next/env@15.5.7': {}
+
+ '@next/eslint-plugin-next@15.5.9':
+ dependencies:
+ fast-glob: 3.3.1
+
+ '@next/swc-darwin-arm64@15.5.7':
+ optional: true
+
+ '@next/swc-darwin-x64@15.5.7':
+ optional: true
+
+ '@next/swc-linux-arm64-gnu@15.5.7':
+ optional: true
+
+ '@next/swc-linux-arm64-musl@15.5.7':
+ optional: true
+
+ '@next/swc-linux-x64-gnu@15.5.7':
+ optional: true
+
+ '@next/swc-linux-x64-musl@15.5.7':
+ optional: true
+
+ '@next/swc-win32-arm64-msvc@15.5.7':
+ optional: true
+
+ '@next/swc-win32-x64-msvc@15.5.7':
+ optional: true
+
+ '@nodelib/fs.scandir@2.1.5':
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+
+ '@nodelib/fs.stat@2.0.5': {}
+
+ '@nodelib/fs.walk@1.2.8':
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.20.1
+
+ '@parcel/watcher-android-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-darwin-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-darwin-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-freebsd-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-arm64-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-x64-glibc@2.5.1':
+ optional: true
+
+ '@parcel/watcher-linux-x64-musl@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-arm64@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-ia32@2.5.1':
+ optional: true
+
+ '@parcel/watcher-win32-x64@2.5.1':
+ optional: true
+
+ '@parcel/watcher@2.5.1':
+ dependencies:
+ detect-libc: 1.0.3
+ is-glob: 4.0.3
+ micromatch: 4.0.8
+ node-addon-api: 7.1.1
+ optionalDependencies:
+ '@parcel/watcher-android-arm64': 2.5.1
+ '@parcel/watcher-darwin-arm64': 2.5.1
+ '@parcel/watcher-darwin-x64': 2.5.1
+ '@parcel/watcher-freebsd-x64': 2.5.1
+ '@parcel/watcher-linux-arm-glibc': 2.5.1
+ '@parcel/watcher-linux-arm-musl': 2.5.1
+ '@parcel/watcher-linux-arm64-glibc': 2.5.1
+ '@parcel/watcher-linux-arm64-musl': 2.5.1
+ '@parcel/watcher-linux-x64-glibc': 2.5.1
+ '@parcel/watcher-linux-x64-musl': 2.5.1
+ '@parcel/watcher-win32-arm64': 2.5.1
+ '@parcel/watcher-win32-ia32': 2.5.1
+ '@parcel/watcher-win32-x64': 2.5.1
+ optional: true
+
+ '@reduxjs/toolkit@2.5.0(react-redux@9.2.0(@types/react@19.0.2)(react@19.2.3)(redux@5.0.1))(react@19.2.3)':
+ dependencies:
+ immer: 10.2.0
+ redux: 5.0.1
+ redux-thunk: 3.1.0(redux@5.0.1)
+ reselect: 5.1.1
+ optionalDependencies:
+ react: 19.2.3
+ react-redux: 9.2.0(@types/react@19.0.2)(react@19.2.3)(redux@5.0.1)
+
+ '@sinclair/typebox@0.27.8': {}
+
+ '@sinonjs/commons@3.0.1':
+ dependencies:
+ type-detect: 4.0.8
+
+ '@sinonjs/fake-timers@10.3.0':
+ dependencies:
+ '@sinonjs/commons': 3.0.1
+
+ '@socket.io/component-emitter@3.1.2': {}
+
+ '@swc/helpers@0.5.15':
+ dependencies:
+ tslib: 2.8.1
+
+ '@testing-library/dom@10.4.0':
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@babel/runtime': 7.28.4
+ '@types/aria-query': 5.0.4
+ aria-query: 5.3.0
+ chalk: 4.1.2
+ dom-accessibility-api: 0.5.16
+ lz-string: 1.5.0
+ pretty-format: 27.5.1
+
+ '@testing-library/jest-dom@6.6.3':
+ dependencies:
+ '@adobe/css-tools': 4.4.4
+ aria-query: 5.3.2
+ chalk: 3.0.0
+ css.escape: 1.5.1
+ dom-accessibility-api: 0.6.3
+ lodash: 4.17.21
+ redent: 3.0.0
+
+ '@testing-library/react@16.1.0(@testing-library/dom@10.4.0)(@types/react-dom@19.0.2(@types/react@19.0.2))(@types/react@19.0.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ dependencies:
+ '@babel/runtime': 7.28.4
+ '@testing-library/dom': 10.4.0
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ optionalDependencies:
+ '@types/react': 19.0.2
+ '@types/react-dom': 19.0.2(@types/react@19.0.2)
+
+ '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)':
+ dependencies:
+ '@testing-library/dom': 10.4.0
+
+ '@tootallnate/once@2.0.0': {}
+
+ '@tootallnate/quickjs-emscripten@0.23.0': {}
+
+ '@tsconfig/node10@1.0.12': {}
+
+ '@tsconfig/node12@1.0.11': {}
+
+ '@tsconfig/node14@1.0.3': {}
+
+ '@tsconfig/node16@1.0.4': {}
+
+ '@turbo/gen@1.13.4(@types/node@22.19.5)(typescript@5.9.3)':
+ dependencies:
+ '@turbo/workspaces': 1.13.4(@types/node@22.19.5)
+ chalk: 2.4.2
+ commander: 10.0.1
+ fs-extra: 10.1.0
+ inquirer: 8.2.7(@types/node@22.19.5)
+ minimatch: 9.0.5
+ node-plop: 0.26.3
+ proxy-agent: 6.5.0
+ ts-node: 10.9.2(@types/node@22.19.5)(typescript@5.9.3)
+ update-check: 1.5.4
+ validate-npm-package-name: 5.0.1
+ transitivePeerDependencies:
+ - '@swc/core'
+ - '@swc/wasm'
+ - '@types/node'
+ - supports-color
+ - typescript
+
+ '@turbo/workspaces@1.13.4(@types/node@22.19.5)':
+ dependencies:
+ chalk: 2.4.2
+ commander: 10.0.1
+ execa: 5.1.1
+ fast-glob: 3.3.3
+ fs-extra: 10.1.0
+ gradient-string: 2.0.2
+ inquirer: 8.2.7(@types/node@22.19.5)
+ js-yaml: 4.1.1
+ ora: 4.1.1
+ rimraf: 3.0.2
+ semver: 7.7.3
+ update-check: 1.5.4
+ transitivePeerDependencies:
+ - '@types/node'
+
+ '@types/aria-query@5.0.4': {}
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.28.5
+
+ '@types/cookie@0.6.0': {}
+
+ '@types/d3-color@3.1.3': {}
+
+ '@types/d3-drag@3.0.7':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-interpolate@3.0.4':
+ dependencies:
+ '@types/d3-color': 3.1.3
+
+ '@types/d3-selection@3.0.11': {}
+
+ '@types/d3-transition@3.0.9':
+ dependencies:
+ '@types/d3-selection': 3.0.11
+
+ '@types/d3-zoom@3.0.8':
+ dependencies:
+ '@types/d3-interpolate': 3.0.4
+ '@types/d3-selection': 3.0.11
+
+ '@types/estree@1.0.8': {}
+
+ '@types/glob@7.2.0':
+ dependencies:
+ '@types/minimatch': 6.0.0
+ '@types/node': 22.19.5
+
+ '@types/graceful-fs@4.1.9':
+ dependencies:
+ '@types/node': 22.19.5
+
+ '@types/hammerjs@2.0.46': {}
+
+ '@types/hoist-non-react-statics@3.3.7(@types/react@19.0.2)':
+ dependencies:
+ '@types/react': 19.0.2
+ hoist-non-react-statics: 3.3.2
+
+ '@types/inquirer@6.5.0':
+ dependencies:
+ '@types/through': 0.0.33
+ rxjs: 6.6.7
+
+ '@types/istanbul-lib-coverage@2.0.6': {}
+
+ '@types/istanbul-lib-report@3.0.3':
+ dependencies:
+ '@types/istanbul-lib-coverage': 2.0.6
+
+ '@types/istanbul-reports@3.0.4':
+ dependencies:
+ '@types/istanbul-lib-report': 3.0.3
+
+ '@types/jest@29.5.14':
+ dependencies:
+ expect: 29.7.0
+ pretty-format: 29.7.0
+
+ '@types/jsdom@20.0.1':
+ dependencies:
+ '@types/node': 22.19.5
+ '@types/tough-cookie': 4.0.5
+ parse5: 7.3.0
+
+ '@types/json-schema@7.0.15': {}
+
+ '@types/lodash.debounce@4.0.9':
+ dependencies:
+ '@types/lodash': 4.17.21
+
+ '@types/lodash@4.17.21': {}
+
+ '@types/luxon@3.4.2': {}
+
+ '@types/minimatch@6.0.0':
+ dependencies:
+ minimatch: 9.0.5
+
+ '@types/negotiator@0.6.4': {}
+
+ '@types/node@22.19.5':
+ dependencies:
+ undici-types: 6.21.0
+
+ '@types/react-dom@19.0.2(@types/react@19.0.2)':
+ dependencies:
+ '@types/react': 19.0.2
+
+ '@types/react@19.0.2':
+ dependencies:
+ csstype: 3.2.3
+
+ '@types/redux-mock-store@1.5.0':
+ dependencies:
+ redux: 4.2.1
+
+ '@types/stack-utils@2.0.3': {}
+
+ '@types/through@0.0.33':
+ dependencies:
+ '@types/node': 22.19.5
+
+ '@types/tinycolor2@1.4.6': {}
+
+ '@types/tough-cookie@4.0.5': {}
+
+ '@types/use-sync-external-store@0.0.6': {}
+
+ '@types/yargs-parser@21.0.3': {}
+
+ '@types/yargs@17.0.35':
+ dependencies:
+ '@types/yargs-parser': 21.0.3
+
+ '@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/regexpp': 4.12.2
+ '@typescript-eslint/parser': 8.52.0(eslint@9.39.2)(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.52.0
+ '@typescript-eslint/type-utils': 8.52.0(eslint@9.39.2)(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.52.0(eslint@9.39.2)(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.52.0
+ eslint: 9.39.2
+ ignore: 7.0.5
+ natural-compare: 1.4.0
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/parser@8.52.0(eslint@9.39.2)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/scope-manager': 8.52.0
+ '@typescript-eslint/types': 8.52.0
+ '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.52.0
+ debug: 4.4.3
+ eslint: 9.39.2
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/project-service@8.52.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.52.0
+ debug: 4.4.3
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/scope-manager@8.52.0':
+ dependencies:
+ '@typescript-eslint/types': 8.52.0
+ '@typescript-eslint/visitor-keys': 8.52.0
+
+ '@typescript-eslint/tsconfig-utils@8.52.0(typescript@5.9.3)':
+ dependencies:
+ typescript: 5.9.3
+
+ '@typescript-eslint/type-utils@8.52.0(eslint@9.39.2)(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/types': 8.52.0
+ '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.52.0(eslint@9.39.2)(typescript@5.9.3)
+ debug: 4.4.3
+ eslint: 9.39.2
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/types@8.52.0': {}
+
+ '@typescript-eslint/typescript-estree@8.52.0(typescript@5.9.3)':
+ dependencies:
+ '@typescript-eslint/project-service': 8.52.0(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.52.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.52.0
+ '@typescript-eslint/visitor-keys': 8.52.0
+ debug: 4.4.3
+ minimatch: 9.0.5
+ semver: 7.7.3
+ tinyglobby: 0.2.15
+ ts-api-utils: 2.4.0(typescript@5.9.3)
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/utils@8.52.0(eslint@9.39.2)(typescript@5.9.3)':
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2)
+ '@typescript-eslint/scope-manager': 8.52.0
+ '@typescript-eslint/types': 8.52.0
+ '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
+ eslint: 9.39.2
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@typescript-eslint/visitor-keys@8.52.0':
+ dependencies:
+ '@typescript-eslint/types': 8.52.0
+ eslint-visitor-keys: 4.2.1
+
+ '@xyflow/react@12.3.6(@types/react@19.0.2)(immer@10.2.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
+ dependencies:
+ '@xyflow/system': 0.0.47
+ classcat: 5.0.5
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ zustand: 4.5.7(@types/react@19.0.2)(immer@10.2.0)(react@19.2.3)
+ transitivePeerDependencies:
+ - '@types/react'
+ - immer
+
+ '@xyflow/system@0.0.47':
+ dependencies:
+ '@types/d3-drag': 3.0.7
+ '@types/d3-selection': 3.0.11
+ '@types/d3-transition': 3.0.9
+ '@types/d3-zoom': 3.0.8
+ d3-drag: 3.0.0
+ d3-selection: 3.0.0
+ d3-zoom: 3.0.0
+
+ abab@2.0.6: {}
+
+ acorn-globals@7.0.1:
+ dependencies:
+ acorn: 8.15.0
+ acorn-walk: 8.3.4
+
+ acorn-jsx@5.3.2(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn-walk@8.3.4:
+ dependencies:
+ acorn: 8.15.0
+
+ acorn@8.15.0: {}
+
+ agent-base@6.0.2:
+ dependencies:
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ agent-base@7.1.4: {}
+
+ aggregate-error@3.1.0:
+ dependencies:
+ clean-stack: 2.2.0
+ indent-string: 4.0.0
+
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ajv@8.17.1:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.0
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
+ ansi-escapes@4.3.2:
+ dependencies:
+ type-fest: 0.21.3
+
+ ansi-regex@5.0.1: {}
+
+ ansi-styles@3.2.1:
+ dependencies:
+ color-convert: 1.9.3
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ ansi-styles@5.2.0: {}
+
+ anymatch@3.1.3:
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+
+ arg@4.1.3: {}
+
+ argparse@1.0.10:
+ dependencies:
+ sprintf-js: 1.0.3
+
+ argparse@2.0.1: {}
+
+ aria-query@5.3.0:
+ dependencies:
+ dequal: 2.0.3
+
+ aria-query@5.3.2: {}
+
+ array-buffer-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ is-array-buffer: 3.0.5
+
+ array-includes@3.1.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ is-string: 1.1.1
+ math-intrinsics: 1.1.0
+
+ array-union@2.1.0: {}
+
+ array.prototype.findlast@1.2.5:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flat@1.3.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.flatmap@1.3.3:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-shim-unscopables: 1.1.0
+
+ array.prototype.tosorted@1.1.4:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-shim-unscopables: 1.1.0
+
+ arraybuffer.prototype.slice@1.0.4:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ is-array-buffer: 3.0.5
+
+ ast-types@0.13.4:
+ dependencies:
+ tslib: 2.8.1
+
+ astral-regex@2.0.0: {}
+
+ async-function@1.0.0: {}
+
+ async@3.2.6: {}
+
+ asynckit@0.4.0: {}
+
+ available-typed-arrays@1.0.7:
+ dependencies:
+ possible-typed-array-names: 1.1.0
+
+ babel-jest@29.7.0(@babel/core@7.28.5):
+ dependencies:
+ '@babel/core': 7.28.5
+ '@jest/transform': 29.7.0
+ '@types/babel__core': 7.20.5
+ babel-plugin-istanbul: 6.1.1
+ babel-preset-jest: 29.6.3(@babel/core@7.28.5)
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ slash: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-istanbul@6.1.1:
+ dependencies:
+ '@babel/helper-plugin-utils': 7.27.1
+ '@istanbuljs/load-nyc-config': 1.1.0
+ '@istanbuljs/schema': 0.1.3
+ istanbul-lib-instrument: 5.2.1
+ test-exclude: 6.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ babel-plugin-jest-hoist@29.6.3:
+ dependencies:
+ '@babel/template': 7.27.2
+ '@babel/types': 7.28.5
+ '@types/babel__core': 7.20.5
+ '@types/babel__traverse': 7.28.0
+
+ babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.5):
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.5)
+ '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.5)
+ '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.5)
+ '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.5)
+ '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.5)
+ '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.5)
+ '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.5)
+ '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.5)
+ '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.5)
+ '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.5)
+ '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.5)
+ '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.5)
+ '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.5)
+ '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.5)
+
+ babel-preset-jest@29.6.3(@babel/core@7.28.5):
+ dependencies:
+ '@babel/core': 7.28.5
+ babel-plugin-jest-hoist: 29.6.3
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5)
+
+ balanced-match@1.0.2: {}
+
+ balanced-match@2.0.0: {}
+
+ base64-js@1.5.1: {}
+
+ baseline-browser-mapping@2.9.14: {}
+
+ basic-ftp@5.1.0: {}
+
+ bl@4.1.0:
+ dependencies:
+ buffer: 5.7.1
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+
+ brace-expansion@1.1.12:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.0.2:
+ dependencies:
+ balanced-match: 1.0.2
+
+ braces@3.0.3:
+ dependencies:
+ fill-range: 7.1.1
+
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.9.14
+ caniuse-lite: 1.0.30001763
+ electron-to-chromium: 1.5.267
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ bs-logger@0.2.6:
+ dependencies:
+ fast-json-stable-stringify: 2.1.0
+
+ bser@2.1.1:
+ dependencies:
+ node-int64: 0.4.0
+
+ buffer-from@1.1.2: {}
+
+ buffer@5.7.1:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
+ call-bind-apply-helpers@1.0.2:
+ dependencies:
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+
+ call-bind@1.0.8:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ get-intrinsic: 1.3.0
+ set-function-length: 1.2.2
+
+ call-bound@1.0.4:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ get-intrinsic: 1.3.0
+
+ callsites@3.1.0: {}
+
+ camel-case@3.0.0:
+ dependencies:
+ no-case: 2.3.2
+ upper-case: 1.1.3
+
+ camelcase@5.3.1: {}
+
+ camelcase@6.3.0: {}
+
+ caniuse-lite@1.0.30001763: {}
+
+ chalk@2.4.2:
+ dependencies:
+ ansi-styles: 3.2.1
+ escape-string-regexp: 1.0.5
+ supports-color: 5.5.0
+
+ chalk@3.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ change-case@3.1.0:
+ dependencies:
+ camel-case: 3.0.0
+ constant-case: 2.0.0
+ dot-case: 2.1.1
+ header-case: 1.0.1
+ is-lower-case: 1.1.3
+ is-upper-case: 1.1.2
+ lower-case: 1.1.4
+ lower-case-first: 1.0.2
+ no-case: 2.3.2
+ param-case: 2.1.1
+ pascal-case: 2.0.1
+ path-case: 2.1.1
+ sentence-case: 2.1.1
+ snake-case: 2.1.0
+ swap-case: 1.1.2
+ title-case: 2.1.1
+ upper-case: 1.1.3
+ upper-case-first: 1.1.2
+
+ char-regex@1.0.2: {}
+
+ chardet@0.7.0: {}
+
+ chardet@2.1.1: {}
+
+ chart.js@4.5.0:
+ dependencies:
+ '@kurkle/color': 0.3.4
+
+ chartjs-adapter-date-fns@3.0.0(chart.js@4.5.0)(date-fns@4.1.0):
+ dependencies:
+ chart.js: 4.5.0
+ date-fns: 4.1.0
+
+ chartjs-plugin-zoom@2.2.0(chart.js@4.5.0):
+ dependencies:
+ '@types/hammerjs': 2.0.46
+ chart.js: 4.5.0
+ hammerjs: 2.0.8
+
+ chokidar@4.0.3:
+ dependencies:
+ readdirp: 4.1.2
+
+ ci-info@3.9.0: {}
+
+ cjs-module-lexer@1.4.3: {}
+
+ classcat@5.0.5: {}
+
+ clean-stack@2.2.0: {}
+
+ cli-cursor@3.1.0:
+ dependencies:
+ restore-cursor: 3.1.0
+
+ cli-spinners@2.9.2: {}
+
+ cli-width@3.0.0: {}
+
+ client-only@0.0.1: {}
+
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
+ clone@1.0.4: {}
+
+ co@4.6.0: {}
+
+ collect-v8-coverage@1.0.3: {}
+
+ color-convert@1.9.3:
+ dependencies:
+ color-name: 1.1.3
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.3: {}
+
+ color-name@1.1.4: {}
+
+ colord@2.9.3: {}
+
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
+ commander@10.0.1: {}
+
+ concat-map@0.0.1: {}
+
+ constant-case@2.0.0:
+ dependencies:
+ snake-case: 2.1.0
+ upper-case: 1.1.3
+
+ convert-source-map@2.0.0: {}
+
+ cookie@0.7.2: {}
+
+ core-js-pure@3.47.0: {}
+
+ cosmiconfig@9.0.0(typescript@5.9.3):
+ dependencies:
+ env-paths: 2.2.1
+ import-fresh: 3.3.1
+ js-yaml: 4.1.1
+ parse-json: 5.2.0
+ optionalDependencies:
+ typescript: 5.9.3
+
+ create-jest@29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3)):
+ dependencies:
+ '@jest/types': 29.6.3
+ chalk: 4.1.2
+ exit: 0.1.2
+ graceful-fs: 4.2.11
+ jest-config: 29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ jest-util: 29.7.0
+ prompts: 2.4.2
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ create-require@1.1.1: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ css-functions-list@3.2.3: {}
+
+ css-property-sort-order-smacss@2.2.0: {}
+
+ css-tree@3.1.0:
+ dependencies:
+ mdn-data: 2.12.2
+ source-map-js: 1.2.1
+
+ css.escape@1.5.1: {}
+
+ cssesc@3.0.0: {}
+
+ cssom@0.3.8: {}
+
+ cssom@0.5.0: {}
+
+ cssstyle@2.3.0:
+ dependencies:
+ cssom: 0.3.8
+
+ csstype@3.2.3: {}
+
+ d3-color@3.1.0: {}
+
+ d3-dispatch@3.0.1: {}
+
+ d3-drag@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-selection: 3.0.0
+
+ d3-ease@3.0.1: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-selection@3.0.0: {}
+
+ d3-timer@3.0.1: {}
+
+ d3-transition@3.0.1(d3-selection@3.0.0):
+ dependencies:
+ d3-color: 3.1.0
+ d3-dispatch: 3.0.1
+ d3-ease: 3.0.1
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-timer: 3.0.1
+
+ d3-zoom@3.0.0:
+ dependencies:
+ d3-dispatch: 3.0.1
+ d3-drag: 3.0.0
+ d3-interpolate: 3.0.1
+ d3-selection: 3.0.0
+ d3-transition: 3.0.1(d3-selection@3.0.0)
+
+ data-uri-to-buffer@6.0.2: {}
+
+ data-urls@3.0.2:
+ dependencies:
+ abab: 2.0.6
+ whatwg-mimetype: 3.0.0
+ whatwg-url: 11.0.0
+
+ data-view-buffer@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-length@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ data-view-byte-offset@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-data-view: 1.0.2
+
+ date-fns@4.1.0: {}
+
+ debug@4.3.7:
+ dependencies:
+ ms: 2.1.3
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ decimal.js@10.6.0: {}
+
+ dedent@1.7.1: {}
+
+ deep-extend@0.6.0: {}
+
+ deep-is@0.1.4: {}
+
+ deepmerge@4.3.1: {}
+
+ defaults@1.0.4:
+ dependencies:
+ clone: 1.0.4
+
+ define-data-property@1.1.4:
+ dependencies:
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ define-properties@1.2.1:
+ dependencies:
+ define-data-property: 1.1.4
+ has-property-descriptors: 1.0.2
+ object-keys: 1.1.1
+
+ degenerator@5.0.1:
+ dependencies:
+ ast-types: 0.13.4
+ escodegen: 2.1.0
+ esprima: 4.0.1
+
+ del@5.1.0:
+ dependencies:
+ globby: 10.0.2
+ graceful-fs: 4.2.11
+ is-glob: 4.0.3
+ is-path-cwd: 2.2.0
+ is-path-inside: 3.0.3
+ p-map: 3.0.0
+ rimraf: 3.0.2
+ slash: 3.0.0
+
+ delayed-stream@1.0.0: {}
+
+ dequal@2.0.3: {}
+
+ detect-libc@1.0.3:
+ optional: true
+
+ detect-libc@2.1.2:
+ optional: true
+
+ detect-newline@3.1.0: {}
+
+ diff-sequences@29.6.3: {}
+
+ diff@4.0.2: {}
+
+ dir-glob@3.0.1:
+ dependencies:
+ path-type: 4.0.0
+
+ doctrine@2.1.0:
+ dependencies:
+ esutils: 2.0.3
+
+ dom-accessibility-api@0.5.16: {}
+
+ dom-accessibility-api@0.6.3: {}
+
+ domexception@4.0.0:
+ dependencies:
+ webidl-conversions: 7.0.0
+
+ dot-case@2.1.1:
+ dependencies:
+ no-case: 2.3.2
+
+ dotenv@16.0.3: {}
+
+ dunder-proto@1.0.1:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-errors: 1.3.0
+ gopd: 1.2.0
+
+ ejs@3.1.10:
+ dependencies:
+ jake: 10.9.4
+
+ electron-to-chromium@1.5.267: {}
+
+ emittery@0.13.1: {}
+
+ emoji-regex@8.0.0: {}
+
+ engine.io-client@6.6.4:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.4.3
+ engine.io-parser: 5.2.3
+ ws: 8.18.3
+ xmlhttprequest-ssl: 2.1.2
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ engine.io-parser@5.2.3: {}
+
+ entities@6.0.1: {}
+
+ env-paths@2.2.1: {}
+
+ error-ex@1.3.4:
+ dependencies:
+ is-arrayish: 0.2.1
+
+ es-abstract@1.24.1:
+ dependencies:
+ array-buffer-byte-length: 1.0.2
+ arraybuffer.prototype.slice: 1.0.4
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ data-view-buffer: 1.0.2
+ data-view-byte-length: 1.0.2
+ data-view-byte-offset: 1.0.1
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ es-set-tostringtag: 2.1.0
+ es-to-primitive: 1.3.0
+ function.prototype.name: 1.1.8
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ get-symbol-description: 1.1.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ internal-slot: 1.1.0
+ is-array-buffer: 3.0.5
+ is-callable: 1.2.7
+ is-data-view: 1.0.2
+ is-negative-zero: 2.0.3
+ is-regex: 1.2.1
+ is-set: 2.0.3
+ is-shared-array-buffer: 1.0.4
+ is-string: 1.1.1
+ is-typed-array: 1.1.15
+ is-weakref: 1.1.1
+ math-intrinsics: 1.1.0
+ object-inspect: 1.13.4
+ object-keys: 1.1.1
+ object.assign: 4.1.7
+ own-keys: 1.0.1
+ regexp.prototype.flags: 1.5.4
+ safe-array-concat: 1.1.3
+ safe-push-apply: 1.0.0
+ safe-regex-test: 1.1.0
+ set-proto: 1.0.0
+ stop-iteration-iterator: 1.1.0
+ string.prototype.trim: 1.2.10
+ string.prototype.trimend: 1.0.9
+ string.prototype.trimstart: 1.0.8
+ typed-array-buffer: 1.0.3
+ typed-array-byte-length: 1.0.3
+ typed-array-byte-offset: 1.0.4
+ typed-array-length: 1.0.7
+ unbox-primitive: 1.1.0
+ which-typed-array: 1.1.19
+
+ es-define-property@1.0.1: {}
+
+ es-errors@1.3.0: {}
+
+ es-iterator-helpers@1.2.2:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-set-tostringtag: 2.1.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ globalthis: 1.0.4
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+ has-proto: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ iterator.prototype: 1.1.5
+ safe-array-concat: 1.1.3
+
+ es-object-atoms@1.1.1:
+ dependencies:
+ es-errors: 1.3.0
+
+ es-set-tostringtag@2.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ es-shim-unscopables@1.1.0:
+ dependencies:
+ hasown: 2.0.2
+
+ es-to-primitive@1.3.0:
+ dependencies:
+ is-callable: 1.2.7
+ is-date-object: 1.1.0
+ is-symbol: 1.1.1
+
+ escalade@3.2.0: {}
+
+ escape-string-regexp@1.0.5: {}
+
+ escape-string-regexp@2.0.0: {}
+
+ escape-string-regexp@4.0.0: {}
+
+ escodegen@2.1.0:
+ dependencies:
+ esprima: 4.0.1
+ estraverse: 5.3.0
+ esutils: 2.0.3
+ optionalDependencies:
+ source-map: 0.6.1
+
+ eslint-config-prettier@10.1.8(eslint@9.39.2):
+ dependencies:
+ eslint: 9.39.2
+
+ eslint-plugin-only-warn@1.1.0: {}
+
+ eslint-plugin-react-hooks@7.0.1(eslint@9.39.2):
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/parser': 7.28.5
+ eslint: 9.39.2
+ hermes-parser: 0.25.1
+ zod: 4.3.5
+ zod-validation-error: 4.0.2(zod@4.3.5)
+ transitivePeerDependencies:
+ - supports-color
+
+ eslint-plugin-react@7.37.5(eslint@9.39.2):
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.findlast: 1.2.5
+ array.prototype.flatmap: 1.3.3
+ array.prototype.tosorted: 1.1.4
+ doctrine: 2.1.0
+ es-iterator-helpers: 1.2.2
+ eslint: 9.39.2
+ estraverse: 5.3.0
+ hasown: 2.0.2
+ jsx-ast-utils: 3.3.5
+ minimatch: 3.1.2
+ object.entries: 1.1.9
+ object.fromentries: 2.0.8
+ object.values: 1.2.1
+ prop-types: 15.8.1
+ resolve: 2.0.0-next.5
+ semver: 6.3.1
+ string.prototype.matchall: 4.0.12
+ string.prototype.repeat: 1.0.0
+
+ eslint-plugin-simple-import-sort@12.1.1(eslint@9.39.2):
+ dependencies:
+ eslint: 9.39.2
+
+ eslint-plugin-turbo@2.7.3(eslint@9.39.2)(turbo@2.5.8):
+ dependencies:
+ dotenv: 16.0.3
+ eslint: 9.39.2
+ turbo: 2.5.8
+
+ eslint-scope@8.4.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint@9.39.2:
+ dependencies:
+ '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2)
+ '@eslint-community/regexpp': 4.12.2
+ '@eslint/config-array': 0.21.1
+ '@eslint/config-helpers': 0.4.2
+ '@eslint/core': 0.17.0
+ '@eslint/eslintrc': 3.3.3
+ '@eslint/js': 9.39.2
+ '@eslint/plugin-kit': 0.4.1
+ '@humanfs/node': 0.16.7
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.7.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ eslint-visitor-keys: 4.2.1
+
+ esprima@4.0.1: {}
+
+ esquery@1.7.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ esutils@2.0.3: {}
+
+ execa@5.1.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 6.0.1
+ human-signals: 2.1.0
+ is-stream: 2.0.1
+ merge-stream: 2.0.0
+ npm-run-path: 4.0.1
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+ strip-final-newline: 2.0.0
+
+ exit@0.1.2: {}
+
+ expect@29.7.0:
+ dependencies:
+ '@jest/expect-utils': 29.7.0
+ jest-get-type: 29.6.3
+ jest-matcher-utils: 29.7.0
+ jest-message-util: 29.7.0
+ jest-util: 29.7.0
+
+ external-editor@3.1.0:
+ dependencies:
+ chardet: 0.7.0
+ iconv-lite: 0.4.24
+ tmp: 0.0.33
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-glob@3.3.1:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-glob@3.3.3:
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.8
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fast-uri@3.1.0: {}
+
+ fastest-levenshtein@1.0.16: {}
+
+ fastq@1.20.1:
+ dependencies:
+ reusify: 1.1.0
+
+ fb-watchman@2.0.2:
+ dependencies:
+ bser: 2.1.1
+
+ fdir@6.5.0(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ figures@3.2.0:
+ dependencies:
+ escape-string-regexp: 1.0.5
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ file-entry-cache@9.1.0:
+ dependencies:
+ flat-cache: 5.0.0
+
+ filelist@1.0.4:
+ dependencies:
+ minimatch: 5.1.6
+
+ fill-range@7.1.1:
+ dependencies:
+ to-regex-range: 5.0.1
+
+ find-up@4.1.0:
+ dependencies:
+ locate-path: 5.0.0
+ path-exists: 4.0.0
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.3.3
+ keyv: 4.5.4
+
+ flat-cache@5.0.0:
+ dependencies:
+ flatted: 3.3.3
+ keyv: 4.5.4
+
+ flatted@3.3.3: {}
+
+ for-each@0.3.5:
+ dependencies:
+ is-callable: 1.2.7
+
+ form-data@4.0.5:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.2
+ mime-types: 2.1.35
+
+ fs-extra@10.1.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.0
+ universalify: 2.0.1
+
+ fs.realpath@1.0.0: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ function-bind@1.1.2: {}
+
+ function.prototype.name@1.1.8:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ functions-have-names: 1.2.3
+ hasown: 2.0.2
+ is-callable: 1.2.7
+
+ functions-have-names@1.2.3: {}
+
+ generator-function@2.0.1: {}
+
+ gensync@1.0.0-beta.2: {}
+
+ get-caller-file@2.0.5: {}
+
+ get-intrinsic@1.3.0:
+ dependencies:
+ call-bind-apply-helpers: 1.0.2
+ es-define-property: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ function-bind: 1.1.2
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ hasown: 2.0.2
+ math-intrinsics: 1.1.0
+
+ get-package-type@0.1.0: {}
+
+ get-proto@1.0.1:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-object-atoms: 1.1.1
+
+ get-stream@6.0.1: {}
+
+ get-symbol-description@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+
+ get-uri@6.0.5:
+ dependencies:
+ basic-ftp: 5.1.0
+ data-uri-to-buffer: 6.0.2
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ glob-parent@5.1.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ glob@7.2.3:
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+
+ global-modules@2.0.0:
+ dependencies:
+ global-prefix: 3.0.0
+
+ global-prefix@3.0.0:
+ dependencies:
+ ini: 1.3.8
+ kind-of: 6.0.3
+ which: 1.3.1
+
+ globals@14.0.0: {}
+
+ globals@15.15.0: {}
+
+ globalthis@1.0.4:
+ dependencies:
+ define-properties: 1.2.1
+ gopd: 1.2.0
+
+ globby@10.0.2:
+ dependencies:
+ '@types/glob': 7.2.0
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.3
+ glob: 7.2.3
+ ignore: 5.3.2
+ merge2: 1.4.1
+ slash: 3.0.0
+
+ globby@11.1.0:
+ dependencies:
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.3
+ ignore: 5.3.2
+ merge2: 1.4.1
+ slash: 3.0.0
+
+ globjoin@0.1.4: {}
+
+ gopd@1.2.0: {}
+
+ graceful-fs@4.2.11: {}
+
+ gradient-string@2.0.2:
+ dependencies:
+ chalk: 4.1.2
+ tinygradient: 1.1.5
+
+ hammerjs@2.0.8: {}
+
+ handlebars@4.7.8:
+ dependencies:
+ minimist: 1.2.8
+ neo-async: 2.6.2
+ source-map: 0.6.1
+ wordwrap: 1.0.0
+ optionalDependencies:
+ uglify-js: 3.19.3
+
+ harmony-reflect@1.6.2: {}
+
+ has-bigints@1.1.0: {}
+
+ has-flag@3.0.0: {}
+
+ has-flag@4.0.0: {}
+
+ has-property-descriptors@1.0.2:
+ dependencies:
+ es-define-property: 1.0.1
+
+ has-proto@1.2.0:
+ dependencies:
+ dunder-proto: 1.0.1
+
+ has-symbols@1.1.0: {}
+
+ has-tostringtag@1.0.2:
+ dependencies:
+ has-symbols: 1.1.0
+
+ hasown@2.0.2:
+ dependencies:
+ function-bind: 1.1.2
+
+ header-case@1.0.1:
+ dependencies:
+ no-case: 2.3.2
+ upper-case: 1.1.3
+
+ hermes-estree@0.25.1: {}
+
+ hermes-parser@0.25.1:
+ dependencies:
+ hermes-estree: 0.25.1
+
+ hoist-non-react-statics@3.3.2:
+ dependencies:
+ react-is: 16.13.1
+
+ html-encoding-sniffer@3.0.0:
+ dependencies:
+ whatwg-encoding: 2.0.0
+
+ html-escaper@2.0.2: {}
+
+ html-parse-stringify@3.0.1:
+ dependencies:
+ void-elements: 3.1.0
+
+ html-tags@3.3.1: {}
+
+ http-proxy-agent@5.0.0:
+ dependencies:
+ '@tootallnate/once': 2.0.0
+ agent-base: 6.0.2
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@5.0.1:
+ dependencies:
+ agent-base: 6.0.2
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ human-signals@2.1.0: {}
+
+ i18next-browser-languagedetector@8.0.2:
+ dependencies:
+ '@babel/runtime': 7.28.4
+
+ i18next-resources-to-backend@1.2.1:
+ dependencies:
+ '@babel/runtime': 7.28.4
+
+ i18next@24.2.0(typescript@5.9.3):
+ dependencies:
+ '@babel/runtime': 7.28.4
+ optionalDependencies:
+ typescript: 5.9.3
+
+ iconv-lite@0.4.24:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ iconv-lite@0.7.2:
+ dependencies:
+ safer-buffer: 2.1.2
+
+ identity-obj-proxy@3.0.0:
+ dependencies:
+ harmony-reflect: 1.6.2
+
+ ieee754@1.2.1: {}
+
+ ignore@5.3.2: {}
+
+ ignore@6.0.2: {}
+
+ ignore@7.0.5: {}
+
+ immer@10.2.0: {}
+
+ immutable@5.1.4: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ import-local@3.2.0:
+ dependencies:
+ pkg-dir: 4.2.0
+ resolve-cwd: 3.0.0
+
+ imurmurhash@0.1.4: {}
+
+ indent-string@4.0.0: {}
+
+ inflight@1.0.6:
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+
+ inherits@2.0.4: {}
+
+ ini@1.3.8: {}
+
+ inquirer@7.3.3:
+ dependencies:
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ cli-cursor: 3.1.0
+ cli-width: 3.0.0
+ external-editor: 3.1.0
+ figures: 3.2.0
+ lodash: 4.17.21
+ mute-stream: 0.0.8
+ run-async: 2.4.1
+ rxjs: 6.6.7
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ through: 2.3.8
+
+ inquirer@8.2.7(@types/node@22.19.5):
+ dependencies:
+ '@inquirer/external-editor': 1.0.3(@types/node@22.19.5)
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ cli-cursor: 3.1.0
+ cli-width: 3.0.0
+ figures: 3.2.0
+ lodash: 4.17.21
+ mute-stream: 0.0.8
+ ora: 5.4.1
+ run-async: 2.4.1
+ rxjs: 7.8.2
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ through: 2.3.8
+ wrap-ansi: 6.2.0
+ transitivePeerDependencies:
+ - '@types/node'
+
+ internal-slot@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ hasown: 2.0.2
+ side-channel: 1.1.0
+
+ ip-address@10.1.0: {}
+
+ is-array-buffer@3.0.5:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ is-arrayish@0.2.1: {}
+
+ is-async-function@2.1.1:
+ dependencies:
+ async-function: 1.0.0
+ call-bound: 1.0.4
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-bigint@1.1.0:
+ dependencies:
+ has-bigints: 1.1.0
+
+ is-boolean-object@1.2.2:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-callable@1.2.7: {}
+
+ is-core-module@2.16.1:
+ dependencies:
+ hasown: 2.0.2
+
+ is-data-view@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ is-typed-array: 1.1.15
+
+ is-date-object@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-extglob@2.1.1: {}
+
+ is-finalizationregistry@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-fullwidth-code-point@3.0.0: {}
+
+ is-generator-fn@2.1.0: {}
+
+ is-generator-function@1.1.2:
+ dependencies:
+ call-bound: 1.0.4
+ generator-function: 2.0.1
+ get-proto: 1.0.1
+ has-tostringtag: 1.0.2
+ safe-regex-test: 1.1.0
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ is-interactive@1.0.0: {}
+
+ is-lower-case@1.1.3:
+ dependencies:
+ lower-case: 1.1.4
+
+ is-map@2.0.3: {}
+
+ is-negative-zero@2.0.3: {}
+
+ is-number-object@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-number@7.0.0: {}
+
+ is-path-cwd@2.2.0: {}
+
+ is-path-inside@3.0.3: {}
+
+ is-plain-object@5.0.0: {}
+
+ is-potential-custom-element-name@1.0.1: {}
+
+ is-regex@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+ hasown: 2.0.2
+
+ is-set@2.0.3: {}
+
+ is-shared-array-buffer@1.0.4:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-stream@2.0.1: {}
+
+ is-string@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-tostringtag: 1.0.2
+
+ is-symbol@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+ has-symbols: 1.1.0
+ safe-regex-test: 1.1.0
+
+ is-typed-array@1.1.15:
+ dependencies:
+ which-typed-array: 1.1.19
+
+ is-unicode-supported@0.1.0: {}
+
+ is-upper-case@1.1.2:
+ dependencies:
+ upper-case: 1.1.3
+
+ is-weakmap@2.0.2: {}
+
+ is-weakref@1.1.1:
+ dependencies:
+ call-bound: 1.0.4
+
+ is-weakset@2.0.4:
+ dependencies:
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+
+ isarray@2.0.5: {}
+
+ isbinaryfile@4.0.10: {}
+
+ isexe@2.0.0: {}
+
+ istanbul-lib-coverage@3.2.2: {}
+
+ istanbul-lib-instrument@5.2.1:
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/parser': 7.28.5
+ '@istanbuljs/schema': 0.1.3
+ istanbul-lib-coverage: 3.2.2
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-lib-instrument@6.0.3:
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/parser': 7.28.5
+ '@istanbuljs/schema': 0.1.3
+ istanbul-lib-coverage: 3.2.2
+ semver: 7.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-lib-report@3.0.1:
+ dependencies:
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
+
+ istanbul-lib-source-maps@4.0.1:
+ dependencies:
+ debug: 4.4.3
+ istanbul-lib-coverage: 3.2.2
+ source-map: 0.6.1
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-reports@3.2.0:
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+
+ iterator.prototype@1.1.5:
+ dependencies:
+ define-data-property: 1.1.4
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ has-symbols: 1.1.0
+ set-function-name: 2.0.2
+
+ jake@10.9.4:
+ dependencies:
+ async: 3.2.6
+ filelist: 1.0.4
+ picocolors: 1.1.1
+
+ jest-changed-files@29.7.0:
+ dependencies:
+ execa: 5.1.1
+ jest-util: 29.7.0
+ p-limit: 3.1.0
+
+ jest-circus@29.7.0:
+ dependencies:
+ '@jest/environment': 29.7.0
+ '@jest/expect': 29.7.0
+ '@jest/test-result': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ chalk: 4.1.2
+ co: 4.6.0
+ dedent: 1.7.1
+ is-generator-fn: 2.1.0
+ jest-each: 29.7.0
+ jest-matcher-utils: 29.7.0
+ jest-message-util: 29.7.0
+ jest-runtime: 29.7.0
+ jest-snapshot: 29.7.0
+ jest-util: 29.7.0
+ p-limit: 3.1.0
+ pretty-format: 29.7.0
+ pure-rand: 6.1.0
+ slash: 3.0.0
+ stack-utils: 2.0.6
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-cli@29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3)):
+ dependencies:
+ '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ '@jest/test-result': 29.7.0
+ '@jest/types': 29.6.3
+ chalk: 4.1.2
+ create-jest: 29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ exit: 0.1.2
+ import-local: 3.2.0
+ jest-config: 29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ yargs: 17.7.2
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ jest-config@29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3)):
+ dependencies:
+ '@babel/core': 7.28.5
+ '@jest/test-sequencer': 29.7.0
+ '@jest/types': 29.6.3
+ babel-jest: 29.7.0(@babel/core@7.28.5)
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ deepmerge: 4.3.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-circus: 29.7.0
+ jest-environment-node: 29.7.0
+ jest-get-type: 29.6.3
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-runner: 29.7.0
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ micromatch: 4.0.8
+ parse-json: 5.2.0
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ strip-json-comments: 3.1.1
+ optionalDependencies:
+ '@types/node': 22.19.5
+ ts-node: 10.9.2(@types/node@22.19.5)(typescript@5.9.3)
+ transitivePeerDependencies:
+ - babel-plugin-macros
+ - supports-color
+
+ jest-diff@29.7.0:
+ dependencies:
+ chalk: 4.1.2
+ diff-sequences: 29.6.3
+ jest-get-type: 29.6.3
+ pretty-format: 29.7.0
+
+ jest-docblock@29.7.0:
+ dependencies:
+ detect-newline: 3.1.0
+
+ jest-each@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ chalk: 4.1.2
+ jest-get-type: 29.6.3
+ jest-util: 29.7.0
+ pretty-format: 29.7.0
+
+ jest-environment-jsdom@29.7.0:
+ dependencies:
+ '@jest/environment': 29.7.0
+ '@jest/fake-timers': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/jsdom': 20.0.1
+ '@types/node': 22.19.5
+ jest-mock: 29.7.0
+ jest-util: 29.7.0
+ jsdom: 20.0.3
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ jest-environment-node@29.7.0:
+ dependencies:
+ '@jest/environment': 29.7.0
+ '@jest/fake-timers': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ jest-mock: 29.7.0
+ jest-util: 29.7.0
+
+ jest-get-type@29.6.3: {}
+
+ jest-haste-map@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ '@types/graceful-fs': 4.1.9
+ '@types/node': 22.19.5
+ anymatch: 3.1.3
+ fb-watchman: 2.0.2
+ graceful-fs: 4.2.11
+ jest-regex-util: 29.6.3
+ jest-util: 29.7.0
+ jest-worker: 29.7.0
+ micromatch: 4.0.8
+ walker: 1.0.8
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ jest-leak-detector@29.7.0:
+ dependencies:
+ jest-get-type: 29.6.3
+ pretty-format: 29.7.0
+
+ jest-matcher-utils@29.7.0:
+ dependencies:
+ chalk: 4.1.2
+ jest-diff: 29.7.0
+ jest-get-type: 29.6.3
+ pretty-format: 29.7.0
+
+ jest-message-util@29.7.0:
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ '@jest/types': 29.6.3
+ '@types/stack-utils': 2.0.3
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ micromatch: 4.0.8
+ pretty-format: 29.7.0
+ slash: 3.0.0
+ stack-utils: 2.0.6
+
+ jest-mock@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ jest-util: 29.7.0
+
+ jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
+ optionalDependencies:
+ jest-resolve: 29.7.0
+
+ jest-regex-util@29.6.3: {}
+
+ jest-resolve-dependencies@29.7.0:
+ dependencies:
+ jest-regex-util: 29.6.3
+ jest-snapshot: 29.7.0
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-resolve@29.7.0:
+ dependencies:
+ chalk: 4.1.2
+ graceful-fs: 4.2.11
+ jest-haste-map: 29.7.0
+ jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0)
+ jest-util: 29.7.0
+ jest-validate: 29.7.0
+ resolve: 1.22.11
+ resolve.exports: 2.0.3
+ slash: 3.0.0
+
+ jest-runner@29.7.0:
+ dependencies:
+ '@jest/console': 29.7.0
+ '@jest/environment': 29.7.0
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ chalk: 4.1.2
+ emittery: 0.13.1
+ graceful-fs: 4.2.11
+ jest-docblock: 29.7.0
+ jest-environment-node: 29.7.0
+ jest-haste-map: 29.7.0
+ jest-leak-detector: 29.7.0
+ jest-message-util: 29.7.0
+ jest-resolve: 29.7.0
+ jest-runtime: 29.7.0
+ jest-util: 29.7.0
+ jest-watcher: 29.7.0
+ jest-worker: 29.7.0
+ p-limit: 3.1.0
+ source-map-support: 0.5.13
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-runtime@29.7.0:
+ dependencies:
+ '@jest/environment': 29.7.0
+ '@jest/fake-timers': 29.7.0
+ '@jest/globals': 29.7.0
+ '@jest/source-map': 29.6.3
+ '@jest/test-result': 29.7.0
+ '@jest/transform': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ chalk: 4.1.2
+ cjs-module-lexer: 1.4.3
+ collect-v8-coverage: 1.0.3
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ jest-haste-map: 29.7.0
+ jest-message-util: 29.7.0
+ jest-mock: 29.7.0
+ jest-regex-util: 29.6.3
+ jest-resolve: 29.7.0
+ jest-snapshot: 29.7.0
+ jest-util: 29.7.0
+ slash: 3.0.0
+ strip-bom: 4.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-snapshot@29.7.0:
+ dependencies:
+ '@babel/core': 7.28.5
+ '@babel/generator': 7.28.5
+ '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5)
+ '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5)
+ '@babel/types': 7.28.5
+ '@jest/expect-utils': 29.7.0
+ '@jest/transform': 29.7.0
+ '@jest/types': 29.6.3
+ babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.5)
+ chalk: 4.1.2
+ expect: 29.7.0
+ graceful-fs: 4.2.11
+ jest-diff: 29.7.0
+ jest-get-type: 29.6.3
+ jest-matcher-utils: 29.7.0
+ jest-message-util: 29.7.0
+ jest-util: 29.7.0
+ natural-compare: 1.4.0
+ pretty-format: 29.7.0
+ semver: 7.7.3
+ transitivePeerDependencies:
+ - supports-color
+
+ jest-util@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ chalk: 4.1.2
+ ci-info: 3.9.0
+ graceful-fs: 4.2.11
+ picomatch: 2.3.1
+
+ jest-validate@29.7.0:
+ dependencies:
+ '@jest/types': 29.6.3
+ camelcase: 6.3.0
+ chalk: 4.1.2
+ jest-get-type: 29.6.3
+ leven: 3.1.0
+ pretty-format: 29.7.0
+
+ jest-watcher@29.7.0:
+ dependencies:
+ '@jest/test-result': 29.7.0
+ '@jest/types': 29.6.3
+ '@types/node': 22.19.5
+ ansi-escapes: 4.3.2
+ chalk: 4.1.2
+ emittery: 0.13.1
+ jest-util: 29.7.0
+ string-length: 4.0.2
+
+ jest-worker@29.7.0:
+ dependencies:
+ '@types/node': 22.19.5
+ jest-util: 29.7.0
+ merge-stream: 2.0.0
+ supports-color: 8.1.1
+
+ jest@29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3)):
+ dependencies:
+ '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ '@jest/types': 29.6.3
+ import-local: 3.2.0
+ jest-cli: 29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ transitivePeerDependencies:
+ - '@types/node'
+ - babel-plugin-macros
+ - supports-color
+ - ts-node
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@3.14.2:
+ dependencies:
+ argparse: 1.0.10
+ esprima: 4.0.1
+
+ js-yaml@4.1.1:
+ dependencies:
+ argparse: 2.0.1
+
+ jsdom@20.0.3:
+ dependencies:
+ abab: 2.0.6
+ acorn: 8.15.0
+ acorn-globals: 7.0.1
+ cssom: 0.5.0
+ cssstyle: 2.3.0
+ data-urls: 3.0.2
+ decimal.js: 10.6.0
+ domexception: 4.0.0
+ escodegen: 2.1.0
+ form-data: 4.0.5
+ html-encoding-sniffer: 3.0.0
+ http-proxy-agent: 5.0.0
+ https-proxy-agent: 5.0.1
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.23
+ parse5: 7.3.0
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 4.1.4
+ w3c-xmlserializer: 4.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 2.0.0
+ whatwg-mimetype: 3.0.0
+ whatwg-url: 11.0.0
+ ws: 8.19.0
+ xml-name-validator: 4.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ jsesc@3.1.0: {}
+
+ json-buffer@3.0.1: {}
+
+ json-parse-even-better-errors@2.3.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-schema-traverse@1.0.0: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ json5@2.2.3: {}
+
+ jsonfile@6.2.0:
+ dependencies:
+ universalify: 2.0.1
+ optionalDependencies:
+ graceful-fs: 4.2.11
+
+ jsx-ast-utils@3.3.5:
+ dependencies:
+ array-includes: 3.1.9
+ array.prototype.flat: 1.3.3
+ object.assign: 4.1.7
+ object.values: 1.2.1
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ kind-of@6.0.3: {}
+
+ kleur@3.0.3: {}
+
+ known-css-properties@0.34.0: {}
+
+ known-css-properties@0.37.0: {}
+
+ leven@3.1.0: {}
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ lines-and-columns@1.2.4: {}
+
+ locate-path@5.0.0:
+ dependencies:
+ p-locate: 4.1.0
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.debounce@4.0.8: {}
+
+ lodash.get@4.4.2: {}
+
+ lodash.isplainobject@4.0.6: {}
+
+ lodash.memoize@4.1.2: {}
+
+ lodash.merge@4.6.2: {}
+
+ lodash.truncate@4.4.2: {}
+
+ lodash@4.17.21: {}
+
+ log-symbols@3.0.0:
+ dependencies:
+ chalk: 2.4.2
+
+ log-symbols@4.1.0:
+ dependencies:
+ chalk: 4.1.2
+ is-unicode-supported: 0.1.0
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
+ lower-case-first@1.0.2:
+ dependencies:
+ lower-case: 1.1.4
+
+ lower-case@1.1.4: {}
+
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lru-cache@7.18.3: {}
+
+ luxon@3.5.0: {}
+
+ lz-string@1.5.0: {}
+
+ make-dir@4.0.0:
+ dependencies:
+ semver: 7.7.3
+
+ make-error@1.3.6: {}
+
+ makeerror@1.0.12:
+ dependencies:
+ tmpl: 1.0.5
+
+ math-intrinsics@1.1.0: {}
+
+ mathml-tag-names@2.1.3: {}
+
+ mdn-data@2.12.2: {}
+
+ mdn-data@2.26.0: {}
+
+ meow@13.2.0: {}
+
+ merge-stream@2.0.0: {}
+
+ merge2@1.4.1: {}
+
+ micromatch@4.0.8:
+ dependencies:
+ braces: 3.0.3
+ picomatch: 2.3.1
+
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mimic-fn@2.1.0: {}
+
+ min-indent@1.0.1: {}
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.12
+
+ minimatch@5.1.6:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimatch@9.0.5:
+ dependencies:
+ brace-expansion: 2.0.2
+
+ minimist@1.2.8: {}
+
+ mkdirp@0.5.6:
+ dependencies:
+ minimist: 1.2.8
+
+ ms@2.1.3: {}
+
+ mute-stream@0.0.8: {}
+
+ nanoid@3.3.11: {}
+
+ natural-compare@1.4.0: {}
+
+ negotiator@1.0.0: {}
+
+ neo-async@2.6.2: {}
+
+ netmask@2.0.2: {}
+
+ next-redux-wrapper@8.1.0(next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.83.0))(react-redux@9.2.0(@types/react@19.0.2)(react@19.2.3)(redux@5.0.1))(react@19.2.3):
+ dependencies:
+ next: 15.5.7(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.83.0)
+ react: 19.2.3
+ react-redux: 9.2.0(@types/react@19.0.2)(react@19.2.3)(redux@5.0.1)
+
+ next@15.5.7(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.83.0):
+ dependencies:
+ '@next/env': 15.5.7
+ '@swc/helpers': 0.5.15
+ caniuse-lite: 1.0.30001763
+ postcss: 8.4.31
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+ styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.3)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 15.5.7
+ '@next/swc-darwin-x64': 15.5.7
+ '@next/swc-linux-arm64-gnu': 15.5.7
+ '@next/swc-linux-arm64-musl': 15.5.7
+ '@next/swc-linux-x64-gnu': 15.5.7
+ '@next/swc-linux-x64-musl': 15.5.7
+ '@next/swc-win32-arm64-msvc': 15.5.7
+ '@next/swc-win32-x64-msvc': 15.5.7
+ sass: 1.83.0
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
+ no-case@2.3.2:
+ dependencies:
+ lower-case: 1.1.4
+
+ node-addon-api@7.1.1:
+ optional: true
+
+ node-int64@0.4.0: {}
+
+ node-plop@0.26.3:
+ dependencies:
+ '@babel/runtime-corejs3': 7.28.4
+ '@types/inquirer': 6.5.0
+ change-case: 3.1.0
+ del: 5.1.0
+ globby: 10.0.2
+ handlebars: 4.7.8
+ inquirer: 7.3.3
+ isbinaryfile: 4.0.10
+ lodash.get: 4.4.2
+ mkdirp: 0.5.6
+ resolve: 1.22.11
+
+ node-releases@2.0.27: {}
+
+ normalize-path@3.0.0: {}
+
+ npm-run-path@4.0.1:
+ dependencies:
+ path-key: 3.1.1
+
+ nwsapi@2.2.23: {}
+
+ object-assign@4.1.1: {}
+
+ object-inspect@1.13.4: {}
+
+ object-keys@1.1.1: {}
+
+ object.assign@4.1.7:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+ has-symbols: 1.1.0
+ object-keys: 1.1.1
+
+ object.entries@1.1.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ object.fromentries@2.0.8:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+
+ object.values@1.2.1:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ once@1.4.0:
+ dependencies:
+ wrappy: 1.0.2
+
+ onetime@5.1.2:
+ dependencies:
+ mimic-fn: 2.1.0
+
+ only-allow@1.2.2:
+ dependencies:
+ which-pm-runs: 1.1.0
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ ora@4.1.1:
+ dependencies:
+ chalk: 3.0.0
+ cli-cursor: 3.1.0
+ cli-spinners: 2.9.2
+ is-interactive: 1.0.0
+ log-symbols: 3.0.0
+ mute-stream: 0.0.8
+ strip-ansi: 6.0.1
+ wcwidth: 1.0.1
+
+ ora@5.4.1:
+ dependencies:
+ bl: 4.1.0
+ chalk: 4.1.2
+ cli-cursor: 3.1.0
+ cli-spinners: 2.9.2
+ is-interactive: 1.0.0
+ is-unicode-supported: 0.1.0
+ log-symbols: 4.1.0
+ strip-ansi: 6.0.1
+ wcwidth: 1.0.1
+
+ os-tmpdir@1.0.2: {}
+
+ own-keys@1.0.1:
+ dependencies:
+ get-intrinsic: 1.3.0
+ object-keys: 1.1.1
+ safe-push-apply: 1.0.0
+
+ p-limit@2.3.0:
+ dependencies:
+ p-try: 2.2.0
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@4.1.0:
+ dependencies:
+ p-limit: 2.3.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ p-map@3.0.0:
+ dependencies:
+ aggregate-error: 3.1.0
+
+ p-try@2.2.0: {}
+
+ pac-proxy-agent@7.2.0:
+ dependencies:
+ '@tootallnate/quickjs-emscripten': 0.23.0
+ agent-base: 7.1.4
+ debug: 4.4.3
+ get-uri: 6.0.5
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ pac-resolver: 7.0.1
+ socks-proxy-agent: 8.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ pac-resolver@7.0.1:
+ dependencies:
+ degenerator: 5.0.1
+ netmask: 2.0.2
+
+ param-case@2.1.1:
+ dependencies:
+ no-case: 2.3.2
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ parse-json@5.2.0:
+ dependencies:
+ '@babel/code-frame': 7.27.1
+ error-ex: 1.3.4
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+
+ parse5@7.3.0:
+ dependencies:
+ entities: 6.0.1
+
+ pascal-case@2.0.1:
+ dependencies:
+ camel-case: 3.0.0
+ upper-case-first: 1.1.2
+
+ path-case@2.1.1:
+ dependencies:
+ no-case: 2.3.2
+
+ path-exists@4.0.0: {}
+
+ path-is-absolute@1.0.1: {}
+
+ path-key@3.1.1: {}
+
+ path-parse@1.0.7: {}
+
+ path-type@4.0.0: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@2.3.1: {}
+
+ picomatch@4.0.3: {}
+
+ pirates@4.0.7: {}
+
+ pkg-dir@4.2.0:
+ dependencies:
+ find-up: 4.1.0
+
+ possible-typed-array-names@1.1.0: {}
+
+ postcss-media-query-parser@0.2.3: {}
+
+ postcss-resolve-nested-selector@0.1.6: {}
+
+ postcss-safe-parser@7.0.1(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+
+ postcss-scss@4.0.9(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+
+ postcss-selector-parser@6.1.2:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-selector-parser@7.1.1:
+ dependencies:
+ cssesc: 3.0.0
+ util-deprecate: 1.0.2
+
+ postcss-sorting@8.0.2(postcss@8.5.6):
+ dependencies:
+ postcss: 8.5.6
+
+ postcss-value-parser@4.2.0: {}
+
+ postcss@8.4.31:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prelude-ls@1.2.1: {}
+
+ prettier@3.4.2: {}
+
+ pretty-format@27.5.1:
+ dependencies:
+ ansi-regex: 5.0.1
+ ansi-styles: 5.2.0
+ react-is: 17.0.2
+
+ pretty-format@29.7.0:
+ dependencies:
+ '@jest/schemas': 29.6.3
+ ansi-styles: 5.2.0
+ react-is: 18.3.1
+
+ prompts@2.4.2:
+ dependencies:
+ kleur: 3.0.3
+ sisteransi: 1.0.5
+
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
+ proxy-agent@6.5.0:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ lru-cache: 7.18.3
+ pac-proxy-agent: 7.2.0
+ proxy-from-env: 1.1.0
+ socks-proxy-agent: 8.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ proxy-from-env@1.1.0: {}
+
+ psl@1.15.0:
+ dependencies:
+ punycode: 2.3.1
+
+ punycode@2.3.1: {}
+
+ pure-rand@6.1.0: {}
+
+ querystringify@2.2.0: {}
+
+ queue-microtask@1.2.3: {}
+
+ rc@1.2.8:
+ dependencies:
+ deep-extend: 0.6.0
+ ini: 1.3.8
+ minimist: 1.2.8
+ strip-json-comments: 2.0.1
+
+ react-chartjs-2@5.3.0(chart.js@4.5.0)(react@19.2.3):
+ dependencies:
+ chart.js: 4.5.0
+ react: 19.2.3
+
+ react-cookie@7.2.2(@types/react@19.0.2)(react@19.2.3):
+ dependencies:
+ '@types/hoist-non-react-statics': 3.3.7(@types/react@19.0.2)
+ hoist-non-react-statics: 3.3.2
+ react: 19.2.3
+ universal-cookie: 7.2.2
+ transitivePeerDependencies:
+ - '@types/react'
+
+ react-dom@19.2.3(react@19.2.3):
+ dependencies:
+ react: 19.2.3
+ scheduler: 0.27.0
+
+ react-i18next@15.4.0(i18next@24.2.0(typescript@5.9.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ dependencies:
+ '@babel/runtime': 7.28.4
+ html-parse-stringify: 3.0.1
+ i18next: 24.2.0(typescript@5.9.3)
+ react: 19.2.3
+ optionalDependencies:
+ react-dom: 19.2.3(react@19.2.3)
+
+ react-is@16.13.1: {}
+
+ react-is@17.0.2: {}
+
+ react-is@18.3.1: {}
+
+ react-redux@9.2.0(@types/react@19.0.2)(react@19.2.3)(redux@5.0.1):
+ dependencies:
+ '@types/use-sync-external-store': 0.0.6
+ react: 19.2.3
+ use-sync-external-store: 1.6.0(react@19.2.3)
+ optionalDependencies:
+ '@types/react': 19.0.2
+ redux: 5.0.1
+
+ react-spinners@0.15.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
+ dependencies:
+ react: 19.2.3
+ react-dom: 19.2.3(react@19.2.3)
+
+ react@19.2.3: {}
+
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
+ readdirp@4.1.2: {}
+
+ redent@3.0.0:
+ dependencies:
+ indent-string: 4.0.0
+ strip-indent: 3.0.0
+
+ redux-mock-store@1.5.5(redux@5.0.1):
+ dependencies:
+ lodash.isplainobject: 4.0.6
+ redux: 5.0.1
+
+ redux-thunk@3.1.0(redux@5.0.1):
+ dependencies:
+ redux: 5.0.1
+
+ redux@4.2.1:
+ dependencies:
+ '@babel/runtime': 7.28.4
+
+ redux@5.0.1: {}
+
+ reflect.getprototypeof@1.0.10:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ get-proto: 1.0.1
+ which-builtin-type: 1.2.1
+
+ regexp.prototype.flags@1.5.4:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-errors: 1.3.0
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ set-function-name: 2.0.2
+
+ registry-auth-token@3.3.2:
+ dependencies:
+ rc: 1.2.8
+ safe-buffer: 5.2.1
+
+ registry-url@3.1.0:
+ dependencies:
+ rc: 1.2.8
+
+ require-directory@2.1.1: {}
+
+ require-from-string@2.0.2: {}
+
+ requires-port@1.0.0: {}
+
+ reselect@5.1.1: {}
+
+ resolve-cwd@3.0.0:
+ dependencies:
+ resolve-from: 5.0.0
+
+ resolve-from@4.0.0: {}
+
+ resolve-from@5.0.0: {}
+
+ resolve.exports@2.0.3: {}
+
+ resolve@1.22.11:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ resolve@2.0.0-next.5:
+ dependencies:
+ is-core-module: 2.16.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+
+ restore-cursor@3.1.0:
+ dependencies:
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+
+ reusify@1.1.0: {}
+
+ rimraf@3.0.2:
+ dependencies:
+ glob: 7.2.3
+
+ run-async@2.4.1: {}
+
+ run-parallel@1.2.0:
+ dependencies:
+ queue-microtask: 1.2.3
+
+ rxjs@6.6.7:
+ dependencies:
+ tslib: 1.14.1
+
+ rxjs@7.8.2:
+ dependencies:
+ tslib: 2.8.1
+
+ safe-array-concat@1.1.3:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ get-intrinsic: 1.3.0
+ has-symbols: 1.1.0
+ isarray: 2.0.5
+
+ safe-buffer@5.2.1: {}
+
+ safe-push-apply@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ isarray: 2.0.5
+
+ safe-regex-test@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-regex: 1.2.1
+
+ safer-buffer@2.1.2: {}
+
+ sass@1.83.0:
+ dependencies:
+ chokidar: 4.0.3
+ immutable: 5.1.4
+ source-map-js: 1.2.1
+ optionalDependencies:
+ '@parcel/watcher': 2.5.1
+
+ saxes@6.0.0:
+ dependencies:
+ xmlchars: 2.2.0
+
+ scheduler@0.27.0: {}
+
+ semver@6.3.1: {}
+
+ semver@7.7.3: {}
+
+ sentence-case@2.1.1:
+ dependencies:
+ no-case: 2.3.2
+ upper-case-first: 1.1.2
+
+ set-function-length@1.2.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ function-bind: 1.1.2
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-property-descriptors: 1.0.2
+
+ set-function-name@2.0.2:
+ dependencies:
+ define-data-property: 1.1.4
+ es-errors: 1.3.0
+ functions-have-names: 1.2.3
+ has-property-descriptors: 1.0.2
+
+ set-proto@1.0.0:
+ dependencies:
+ dunder-proto: 1.0.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+
+ sharp@0.34.5:
+ dependencies:
+ '@img/colour': 1.0.0
+ detect-libc: 2.1.2
+ semver: 7.7.3
+ optionalDependencies:
+ '@img/sharp-darwin-arm64': 0.34.5
+ '@img/sharp-darwin-x64': 0.34.5
+ '@img/sharp-libvips-darwin-arm64': 1.2.4
+ '@img/sharp-libvips-darwin-x64': 1.2.4
+ '@img/sharp-libvips-linux-arm': 1.2.4
+ '@img/sharp-libvips-linux-arm64': 1.2.4
+ '@img/sharp-libvips-linux-ppc64': 1.2.4
+ '@img/sharp-libvips-linux-riscv64': 1.2.4
+ '@img/sharp-libvips-linux-s390x': 1.2.4
+ '@img/sharp-libvips-linux-x64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-arm64': 1.2.4
+ '@img/sharp-libvips-linuxmusl-x64': 1.2.4
+ '@img/sharp-linux-arm': 0.34.5
+ '@img/sharp-linux-arm64': 0.34.5
+ '@img/sharp-linux-ppc64': 0.34.5
+ '@img/sharp-linux-riscv64': 0.34.5
+ '@img/sharp-linux-s390x': 0.34.5
+ '@img/sharp-linux-x64': 0.34.5
+ '@img/sharp-linuxmusl-arm64': 0.34.5
+ '@img/sharp-linuxmusl-x64': 0.34.5
+ '@img/sharp-wasm32': 0.34.5
+ '@img/sharp-win32-arm64': 0.34.5
+ '@img/sharp-win32-ia32': 0.34.5
+ '@img/sharp-win32-x64': 0.34.5
+ optional: true
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ side-channel-list@1.0.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-map@1.0.1:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+
+ side-channel-weakmap@1.0.2:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ get-intrinsic: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-map: 1.0.1
+
+ side-channel@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ object-inspect: 1.13.4
+ side-channel-list: 1.0.0
+ side-channel-map: 1.0.1
+ side-channel-weakmap: 1.0.2
+
+ signal-exit@3.0.7: {}
+
+ signal-exit@4.1.0: {}
+
+ sisteransi@1.0.5: {}
+
+ slash@3.0.0: {}
+
+ slice-ansi@4.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ astral-regex: 2.0.0
+ is-fullwidth-code-point: 3.0.0
+
+ smart-buffer@4.2.0: {}
+
+ snake-case@2.1.0:
+ dependencies:
+ no-case: 2.3.2
+
+ socket.io-client@4.8.1:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ engine.io-client: 6.6.4
+ socket.io-parser: 4.2.5
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ socket.io-parser@4.2.5:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ socks-proxy-agent@8.0.5:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ socks: 2.8.7
+ transitivePeerDependencies:
+ - supports-color
+
+ socks@2.8.7:
+ dependencies:
+ ip-address: 10.1.0
+ smart-buffer: 4.2.0
+
+ source-map-js@1.2.1: {}
+
+ source-map-support@0.5.13:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+
+ source-map@0.6.1: {}
+
+ sprintf-js@1.0.3: {}
+
+ stack-utils@2.0.6:
+ dependencies:
+ escape-string-regexp: 2.0.0
+
+ stop-iteration-iterator@1.1.0:
+ dependencies:
+ es-errors: 1.3.0
+ internal-slot: 1.1.0
+
+ string-length@4.0.2:
+ dependencies:
+ char-regex: 1.0.2
+ strip-ansi: 6.0.1
+
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string.prototype.matchall@4.0.12:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-errors: 1.3.0
+ es-object-atoms: 1.1.1
+ get-intrinsic: 1.3.0
+ gopd: 1.2.0
+ has-symbols: 1.1.0
+ internal-slot: 1.1.0
+ regexp.prototype.flags: 1.5.4
+ set-function-name: 2.0.2
+ side-channel: 1.1.0
+
+ string.prototype.repeat@1.0.0:
+ dependencies:
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+
+ string.prototype.trim@1.2.10:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-data-property: 1.1.4
+ define-properties: 1.2.1
+ es-abstract: 1.24.1
+ es-object-atoms: 1.1.1
+ has-property-descriptors: 1.0.2
+
+ string.prototype.trimend@1.0.9:
+ dependencies:
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ string.prototype.trimstart@1.0.8:
+ dependencies:
+ call-bind: 1.0.8
+ define-properties: 1.2.1
+ es-object-atoms: 1.1.1
+
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-bom@4.0.0: {}
+
+ strip-final-newline@2.0.0: {}
+
+ strip-indent@3.0.0:
+ dependencies:
+ min-indent: 1.0.1
+
+ strip-json-comments@2.0.1: {}
+
+ strip-json-comments@3.1.1: {}
+
+ styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.3):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.3
+ optionalDependencies:
+ '@babel/core': 7.28.5
+
+ stylelint-config-prettier-scss@1.0.0(stylelint@16.10.0(typescript@5.9.3)):
+ dependencies:
+ stylelint: 16.10.0(typescript@5.9.3)
+
+ stylelint-config-property-sort-order-smacss@10.0.0(stylelint@16.10.0(typescript@5.9.3)):
+ dependencies:
+ css-property-sort-order-smacss: 2.2.0
+ stylelint: 16.10.0(typescript@5.9.3)
+ stylelint-order: 6.0.4(stylelint@16.10.0(typescript@5.9.3))
+
+ stylelint-config-recommended-scss@14.1.0(postcss@8.5.6)(stylelint@16.10.0(typescript@5.9.3)):
+ dependencies:
+ postcss-scss: 4.0.9(postcss@8.5.6)
+ stylelint: 16.10.0(typescript@5.9.3)
+ stylelint-config-recommended: 14.0.1(stylelint@16.10.0(typescript@5.9.3))
+ stylelint-scss: 6.14.0(stylelint@16.10.0(typescript@5.9.3))
+ optionalDependencies:
+ postcss: 8.5.6
+
+ stylelint-config-recommended@14.0.1(stylelint@16.10.0(typescript@5.9.3)):
+ dependencies:
+ stylelint: 16.10.0(typescript@5.9.3)
+
+ stylelint-config-standard-scss@13.1.0(postcss@8.5.6)(stylelint@16.10.0(typescript@5.9.3)):
+ dependencies:
+ stylelint: 16.10.0(typescript@5.9.3)
+ stylelint-config-recommended-scss: 14.1.0(postcss@8.5.6)(stylelint@16.10.0(typescript@5.9.3))
+ stylelint-config-standard: 36.0.1(stylelint@16.10.0(typescript@5.9.3))
+ optionalDependencies:
+ postcss: 8.5.6
+
+ stylelint-config-standard@36.0.1(stylelint@16.10.0(typescript@5.9.3)):
+ dependencies:
+ stylelint: 16.10.0(typescript@5.9.3)
+ stylelint-config-recommended: 14.0.1(stylelint@16.10.0(typescript@5.9.3))
+
+ stylelint-order@6.0.4(stylelint@16.10.0(typescript@5.9.3)):
+ dependencies:
+ postcss: 8.5.6
+ postcss-sorting: 8.0.2(postcss@8.5.6)
+ stylelint: 16.10.0(typescript@5.9.3)
+
+ stylelint-scss@6.14.0(stylelint@16.10.0(typescript@5.9.3)):
+ dependencies:
+ css-tree: 3.1.0
+ is-plain-object: 5.0.0
+ known-css-properties: 0.37.0
+ mdn-data: 2.26.0
+ postcss-media-query-parser: 0.2.3
+ postcss-resolve-nested-selector: 0.1.6
+ postcss-selector-parser: 7.1.1
+ postcss-value-parser: 4.2.0
+ stylelint: 16.10.0(typescript@5.9.3)
+
+ stylelint@16.10.0(typescript@5.9.3):
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ '@csstools/media-query-list-parser': 3.0.1(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/selector-specificity': 4.0.0(postcss-selector-parser@6.1.2)
+ '@dual-bundle/import-meta-resolve': 4.2.1
+ balanced-match: 2.0.0
+ colord: 2.9.3
+ cosmiconfig: 9.0.0(typescript@5.9.3)
+ css-functions-list: 3.2.3
+ css-tree: 3.1.0
+ debug: 4.4.3
+ fast-glob: 3.3.3
+ fastest-levenshtein: 1.0.16
+ file-entry-cache: 9.1.0
+ global-modules: 2.0.0
+ globby: 11.1.0
+ globjoin: 0.1.4
+ html-tags: 3.3.1
+ ignore: 6.0.2
+ imurmurhash: 0.1.4
+ is-plain-object: 5.0.0
+ known-css-properties: 0.34.0
+ mathml-tag-names: 2.1.3
+ meow: 13.2.0
+ micromatch: 4.0.8
+ normalize-path: 3.0.0
+ picocolors: 1.1.1
+ postcss: 8.5.6
+ postcss-resolve-nested-selector: 0.1.6
+ postcss-safe-parser: 7.0.1(postcss@8.5.6)
+ postcss-selector-parser: 6.1.2
+ postcss-value-parser: 4.2.0
+ resolve-from: 5.0.0
+ string-width: 4.2.3
+ supports-hyperlinks: 3.2.0
+ svg-tags: 1.0.0
+ table: 6.9.0
+ write-file-atomic: 5.0.1
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+
+ supports-color@5.5.0:
+ dependencies:
+ has-flag: 3.0.0
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-color@8.1.1:
+ dependencies:
+ has-flag: 4.0.0
+
+ supports-hyperlinks@3.2.0:
+ dependencies:
+ has-flag: 4.0.0
+ supports-color: 7.2.0
+
+ supports-preserve-symlinks-flag@1.0.0: {}
+
+ svg-tags@1.0.0: {}
+
+ swap-case@1.1.2:
+ dependencies:
+ lower-case: 1.1.4
+ upper-case: 1.1.3
+
+ swr-models@1.0.2(react@19.2.3)(swr@2.3.8(react@19.2.3)):
+ dependencies:
+ react: 19.2.3
+ swr: 2.3.8(react@19.2.3)
+
+ swr@2.3.8(react@19.2.3):
+ dependencies:
+ dequal: 2.0.3
+ react: 19.2.3
+ use-sync-external-store: 1.6.0(react@19.2.3)
+
+ symbol-tree@3.2.4: {}
+
+ table@6.9.0:
+ dependencies:
+ ajv: 8.17.1
+ lodash.truncate: 4.4.2
+ slice-ansi: 4.0.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ test-exclude@6.0.0:
+ dependencies:
+ '@istanbuljs/schema': 0.1.3
+ glob: 7.2.3
+ minimatch: 3.1.2
+
+ through@2.3.8: {}
+
+ tinycolor2@1.6.0: {}
+
+ tinyglobby@0.2.15:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ tinygradient@1.1.5:
+ dependencies:
+ '@types/tinycolor2': 1.4.6
+ tinycolor2: 1.6.0
+
+ title-case@2.1.1:
+ dependencies:
+ no-case: 2.3.2
+ upper-case: 1.1.3
+
+ tmp@0.0.33:
+ dependencies:
+ os-tmpdir: 1.0.2
+
+ tmpl@1.0.5: {}
+
+ to-regex-range@5.0.1:
+ dependencies:
+ is-number: 7.0.0
+
+ tough-cookie@4.1.4:
+ dependencies:
+ psl: 1.15.0
+ punycode: 2.3.1
+ universalify: 0.2.0
+ url-parse: 1.5.10
+
+ tr46@3.0.0:
+ dependencies:
+ punycode: 2.3.1
+
+ ts-api-utils@2.4.0(typescript@5.9.3):
+ dependencies:
+ typescript: 5.9.3
+
+ ts-jest@29.2.5(@babel/core@7.28.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.28.5))(jest@29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3)))(typescript@5.9.3):
+ dependencies:
+ bs-logger: 0.2.6
+ ejs: 3.1.10
+ fast-json-stable-stringify: 2.1.0
+ jest: 29.7.0(@types/node@22.19.5)(ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3))
+ jest-util: 29.7.0
+ json5: 2.2.3
+ lodash.memoize: 4.1.2
+ make-error: 1.3.6
+ semver: 7.7.3
+ typescript: 5.9.3
+ yargs-parser: 21.1.1
+ optionalDependencies:
+ '@babel/core': 7.28.5
+ '@jest/transform': 29.7.0
+ '@jest/types': 29.6.3
+ babel-jest: 29.7.0(@babel/core@7.28.5)
+
+ ts-node@10.9.2(@types/node@22.19.5)(typescript@5.9.3):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.12
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 22.19.5
+ acorn: 8.15.0
+ acorn-walk: 8.3.4
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 5.9.3
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+
+ tslib@1.14.1: {}
+
+ tslib@2.8.1: {}
+
+ turbo-darwin-64@2.5.8:
+ optional: true
+
+ turbo-darwin-arm64@2.5.8:
+ optional: true
+
+ turbo-linux-64@2.5.8:
+ optional: true
+
+ turbo-linux-arm64@2.5.8:
+ optional: true
+
+ turbo-windows-64@2.5.8:
+ optional: true
+
+ turbo-windows-arm64@2.5.8:
+ optional: true
+
+ turbo@2.5.8:
+ optionalDependencies:
+ turbo-darwin-64: 2.5.8
+ turbo-darwin-arm64: 2.5.8
+ turbo-linux-64: 2.5.8
+ turbo-linux-arm64: 2.5.8
+ turbo-windows-64: 2.5.8
+ turbo-windows-arm64: 2.5.8
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ type-detect@4.0.8: {}
+
+ type-fest@0.21.3: {}
+
+ typed-array-buffer@1.0.3:
+ dependencies:
+ call-bound: 1.0.4
+ es-errors: 1.3.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-length@1.0.3:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+
+ typed-array-byte-offset@1.0.4:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ has-proto: 1.2.0
+ is-typed-array: 1.1.15
+ reflect.getprototypeof: 1.0.10
+
+ typed-array-length@1.0.7:
+ dependencies:
+ call-bind: 1.0.8
+ for-each: 0.3.5
+ gopd: 1.2.0
+ is-typed-array: 1.1.15
+ possible-typed-array-names: 1.1.0
+ reflect.getprototypeof: 1.0.10
+
+ typescript-eslint@8.52.0(eslint@9.39.2)(typescript@5.9.3):
+ dependencies:
+ '@typescript-eslint/eslint-plugin': 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.52.0(eslint@9.39.2)(typescript@5.9.3)
+ '@typescript-eslint/typescript-estree': 8.52.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.52.0(eslint@9.39.2)(typescript@5.9.3)
+ eslint: 9.39.2
+ typescript: 5.9.3
+ transitivePeerDependencies:
+ - supports-color
+
+ typescript@5.9.3: {}
+
+ uglify-js@3.19.3:
+ optional: true
+
+ unbox-primitive@1.1.0:
+ dependencies:
+ call-bound: 1.0.4
+ has-bigints: 1.1.0
+ has-symbols: 1.1.0
+ which-boxed-primitive: 1.1.1
+
+ undici-types@6.21.0: {}
+
+ universal-cookie@7.2.2:
+ dependencies:
+ '@types/cookie': 0.6.0
+ cookie: 0.7.2
+
+ universalify@0.2.0: {}
+
+ universalify@2.0.1: {}
+
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
+ update-check@1.5.4:
+ dependencies:
+ registry-auth-token: 3.3.2
+ registry-url: 3.1.0
+
+ upper-case-first@1.1.2:
+ dependencies:
+ upper-case: 1.1.3
+
+ upper-case@1.1.3: {}
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ url-parse@1.5.10:
+ dependencies:
+ querystringify: 2.2.0
+ requires-port: 1.0.0
+
+ use-sync-external-store@1.6.0(react@19.2.3):
+ dependencies:
+ react: 19.2.3
+
+ util-deprecate@1.0.2: {}
+
+ v8-compile-cache-lib@3.0.1: {}
+
+ v8-to-istanbul@9.3.0:
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.31
+ '@types/istanbul-lib-coverage': 2.0.6
+ convert-source-map: 2.0.0
+
+ validate-npm-package-name@5.0.1: {}
+
+ void-elements@3.1.0: {}
+
+ w3c-xmlserializer@4.0.0:
+ dependencies:
+ xml-name-validator: 4.0.0
+
+ walker@1.0.8:
+ dependencies:
+ makeerror: 1.0.12
+
+ wcwidth@1.0.1:
+ dependencies:
+ defaults: 1.0.4
+
+ webidl-conversions@7.0.0: {}
+
+ whatwg-encoding@2.0.0:
+ dependencies:
+ iconv-lite: 0.6.3
+
+ whatwg-mimetype@3.0.0: {}
+
+ whatwg-url@11.0.0:
+ dependencies:
+ tr46: 3.0.0
+ webidl-conversions: 7.0.0
+
+ which-boxed-primitive@1.1.1:
+ dependencies:
+ is-bigint: 1.1.0
+ is-boolean-object: 1.2.2
+ is-number-object: 1.1.1
+ is-string: 1.1.1
+ is-symbol: 1.1.1
+
+ which-builtin-type@1.2.1:
+ dependencies:
+ call-bound: 1.0.4
+ function.prototype.name: 1.1.8
+ has-tostringtag: 1.0.2
+ is-async-function: 2.1.1
+ is-date-object: 1.1.0
+ is-finalizationregistry: 1.1.1
+ is-generator-function: 1.1.2
+ is-regex: 1.2.1
+ is-weakref: 1.1.1
+ isarray: 2.0.5
+ which-boxed-primitive: 1.1.1
+ which-collection: 1.0.2
+ which-typed-array: 1.1.19
+
+ which-collection@1.0.2:
+ dependencies:
+ is-map: 2.0.3
+ is-set: 2.0.3
+ is-weakmap: 2.0.2
+ is-weakset: 2.0.4
+
+ which-pm-runs@1.1.0: {}
+
+ which-typed-array@1.1.19:
+ dependencies:
+ available-typed-arrays: 1.0.7
+ call-bind: 1.0.8
+ call-bound: 1.0.4
+ for-each: 0.3.5
+ get-proto: 1.0.1
+ gopd: 1.2.0
+ has-tostringtag: 1.0.2
+
+ which@1.3.1:
+ dependencies:
+ isexe: 2.0.0
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ word-wrap@1.2.5: {}
+
+ wordwrap@1.0.0: {}
+
+ wrap-ansi@6.2.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ wrappy@1.0.2: {}
+
+ write-file-atomic@4.0.2:
+ dependencies:
+ imurmurhash: 0.1.4
+ signal-exit: 3.0.7
+
+ write-file-atomic@5.0.1:
+ dependencies:
+ imurmurhash: 0.1.4
+ signal-exit: 4.1.0
+
+ ws@8.18.3: {}
+
+ ws@8.19.0: {}
+
+ xml-name-validator@4.0.0: {}
+
+ xmlchars@2.2.0: {}
+
+ xmlhttprequest-ssl@2.1.2: {}
+
+ y18n@5.0.8: {}
+
+ yallist@3.1.1: {}
+
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
+ yn@3.1.1: {}
+
+ yocto-queue@0.1.0: {}
+
+ zod-validation-error@4.0.2(zod@4.3.5):
+ dependencies:
+ zod: 4.3.5
+
+ zod@4.3.5: {}
+
+ zustand@4.5.7(@types/react@19.0.2)(immer@10.2.0)(react@19.2.3):
+ dependencies:
+ use-sync-external-store: 1.6.0(react@19.2.3)
+ optionalDependencies:
+ '@types/react': 19.0.2
+ immer: 10.2.0
+ react: 19.2.3
diff --git a/src/client/pnpm-workspace.yaml b/src/client/pnpm-workspace.yaml
new file mode 100644
index 0000000..3ff5faa
--- /dev/null
+++ b/src/client/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+packages:
+ - "apps/*"
+ - "packages/*"
diff --git a/src/client/turbo.json b/src/client/turbo.json
new file mode 100644
index 0000000..0367065
--- /dev/null
+++ b/src/client/turbo.json
@@ -0,0 +1,27 @@
+{
+ "$schema": "https://turbo.build/schema.json",
+ "ui": "tui",
+ "tasks": {
+ "build": {
+ "dependsOn": ["^build"],
+ "inputs": ["$TURBO_DEFAULT$", ".env*"],
+ "outputs": [".next/**", "!.next/cache/**"]
+ },
+ "lint": {
+ "dependsOn": ["^lint"]
+ },
+ "dev": {
+ "cache": false,
+ "persistent": true
+ },
+ "test": {},
+ "stylelint": {
+ "dependsOn": ["^stylelint"]
+ }
+ },
+ "globalEnv": [
+ "NEXT_PUBLIC_API_SERVER_HOST",
+ "NEXT_PUBLIC_API_SERVER_PORT",
+ "NEXT_PUBLIC_API_SERVER_PROTOCOL"
+ ]
+}
diff --git a/src/docker-compose.yml b/src/docker-compose.yml
new file mode 100644
index 0000000..9d20f89
--- /dev/null
+++ b/src/docker-compose.yml
@@ -0,0 +1,19 @@
+services:
+ client:
+ build:
+ context: client
+ dockerfile: ./apps/contwatch-client/Dockerfile
+ args:
+ TURBO_TEAM: "$TURBO_TEAM"
+ TURBO_TOKEN: "$TURBO_TOKEN"
+ restart: always
+ network_mode: host
+ api:
+ build:
+ context: server
+ restart: always
+ working_dir: /api
+ volumes:
+ - ./server/logs:/api/logs
+ network_mode: host
+ privileged: true
diff --git a/src/server/Dockerfile b/src/server/Dockerfile
new file mode 100644
index 0000000..f51e5f6
--- /dev/null
+++ b/src/server/Dockerfile
@@ -0,0 +1,44 @@
+FROM python:3.12-slim AS base
+
+FROM base AS builder
+
+WORKDIR /api
+
+RUN apt update \
+ && apt install -y build-essential libpq-dev \
+ && pip3 install pipenv --no-cache-dir --disable-pip-version-check --root-user-action=ignore
+
+# Allows docker to cache installed dependencies between builds
+COPY "Pipfile" "Pipfile.lock" ./
+RUN pipenv install --system --deploy --ignore-pipfile \
+ && pipenv --clear
+
+RUN apt remove -y build-essential \
+ && apt autoremove -y \
+ && apt clean \
+ && for dir in pip pipenv setuptools virtualenv docs distlib pkg_resources packaging certify; \
+ do rm -rf "/usr/local/lib/python3.12/site-packages/$dir"*; done
+
+FROM base AS runner
+
+COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
+COPY --from=builder /usr/local/bin /usr/local/bin
+
+WORKDIR /api
+COPY --from=builder /api ./
+
+RUN apt update \
+ && apt install -y libpq-dev
+# && ln -s "/usr/lib/libcrypto.so.3" "/usr/lib/libcrypto.so" \
+# && pip3 install pipenv --no-cache-dir --disable-pip-version-check --root-user-action=ignore
+# && crontab /api/cron
+
+# Mounts the application code to the image
+COPY . ./
+
+ARG PORT
+ENV PORT=${PORT}
+ENV PYTHONUNBUFFERED=1
+
+# runs the production server
+CMD ["sh", "-c", "./main.py"]
diff --git a/src/server/Pipfile b/src/server/Pipfile
new file mode 100644
index 0000000..0a36de4
--- /dev/null
+++ b/src/server/Pipfile
@@ -0,0 +1,28 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+flask = "*"
+flask-cors = "*"
+flask-socketio = "*"
+minimalmodbus = "*"
+pipdeptree = "*"
+pony = "*"
+pyserial = "*"
+requests = "*"
+simplejson = "*"
+psycopg2 = "*"
+
+[dev-packages]
+black = "*"
+
+[requires]
+python_version = "3.12"
+
+[scripts]
+black = "black --check --line-length 120 ."
+black_fix = "black --line-length 120 ."
+server = "./main.py"
+screen = "screen -S contwatch-server -m python main.py"
diff --git a/src/server/Pipfile.lock b/src/server/Pipfile.lock
new file mode 100644
index 0000000..eebf37b
--- /dev/null
+++ b/src/server/Pipfile.lock
@@ -0,0 +1,587 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "14145d37a26b5df58443d6636502097a15338c519b983adb72a183e20da7549d"
+ },
+ "pipfile-spec": 6,
+ "requires": {
+ "python_version": "3.12"
+ },
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "bidict": {
+ "hashes": [
+ "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71",
+ "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==0.23.1"
+ },
+ "blinker": {
+ "hashes": [
+ "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf",
+ "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc"
+ ],
+ "markers": "python_version >= '3.9'",
+ "version": "==1.9.0"
+ },
+ "certifi": {
+ "hashes": [
+ "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56",
+ "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==2024.12.14"
+ },
+ "charset-normalizer": {
+ "hashes": [
+ "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537",
+ "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa",
+ "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a",
+ "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294",
+ "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b",
+ "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd",
+ "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601",
+ "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd",
+ "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4",
+ "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d",
+ "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2",
+ "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313",
+ "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd",
+ "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa",
+ "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8",
+ "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1",
+ "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2",
+ "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496",
+ "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d",
+ "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b",
+ "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e",
+ "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a",
+ "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4",
+ "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca",
+ "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78",
+ "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408",
+ "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5",
+ "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3",
+ "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f",
+ "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a",
+ "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765",
+ "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6",
+ "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146",
+ "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6",
+ "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9",
+ "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd",
+ "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c",
+ "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f",
+ "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545",
+ "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176",
+ "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770",
+ "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824",
+ "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f",
+ "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf",
+ "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487",
+ "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d",
+ "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd",
+ "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b",
+ "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534",
+ "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f",
+ "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b",
+ "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9",
+ "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd",
+ "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125",
+ "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9",
+ "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de",
+ "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11",
+ "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d",
+ "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35",
+ "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f",
+ "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda",
+ "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7",
+ "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a",
+ "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971",
+ "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8",
+ "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41",
+ "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d",
+ "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f",
+ "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757",
+ "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a",
+ "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886",
+ "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77",
+ "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76",
+ "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247",
+ "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85",
+ "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb",
+ "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7",
+ "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e",
+ "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6",
+ "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037",
+ "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1",
+ "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e",
+ "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807",
+ "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407",
+ "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c",
+ "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12",
+ "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3",
+ "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089",
+ "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd",
+ "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e",
+ "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00",
+ "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==3.4.1"
+ },
+ "click": {
+ "hashes": [
+ "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2",
+ "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==8.1.8"
+ },
+ "flask": {
+ "hashes": [
+ "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac",
+ "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.9'",
+ "version": "==3.1.0"
+ },
+ "flask-cors": {
+ "hashes": [
+ "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef",
+ "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc"
+ ],
+ "index": "pypi",
+ "version": "==5.0.0"
+ },
+ "flask-socketio": {
+ "hashes": [
+ "sha256:35a50166db44d055f68021d6ec32cb96f1f925cd82de4504314be79139ea846f",
+ "sha256:d946c944a1074ccad8e99485a6f5c79bc5789e3ea4df0bb9c864939586c51ec4"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.6'",
+ "version": "==5.5.1"
+ },
+ "h11": {
+ "hashes": [
+ "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d",
+ "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==0.14.0"
+ },
+ "idna": {
+ "hashes": [
+ "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
+ "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==3.10"
+ },
+ "itsdangerous": {
+ "hashes": [
+ "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef",
+ "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==2.2.0"
+ },
+ "jinja2": {
+ "hashes": [
+ "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb",
+ "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==3.1.5"
+ },
+ "markupsafe": {
+ "hashes": [
+ "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4",
+ "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30",
+ "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0",
+ "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9",
+ "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396",
+ "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13",
+ "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028",
+ "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca",
+ "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557",
+ "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832",
+ "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0",
+ "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b",
+ "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579",
+ "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a",
+ "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c",
+ "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff",
+ "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c",
+ "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22",
+ "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094",
+ "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb",
+ "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e",
+ "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5",
+ "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a",
+ "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d",
+ "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a",
+ "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b",
+ "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8",
+ "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225",
+ "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c",
+ "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144",
+ "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f",
+ "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87",
+ "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d",
+ "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93",
+ "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf",
+ "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158",
+ "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84",
+ "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb",
+ "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48",
+ "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171",
+ "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c",
+ "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6",
+ "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd",
+ "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d",
+ "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1",
+ "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d",
+ "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca",
+ "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a",
+ "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29",
+ "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe",
+ "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798",
+ "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c",
+ "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8",
+ "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f",
+ "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f",
+ "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a",
+ "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178",
+ "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0",
+ "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79",
+ "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430",
+ "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"
+ ],
+ "markers": "python_version >= '3.9'",
+ "version": "==3.0.2"
+ },
+ "minimalmodbus": {
+ "hashes": [
+ "sha256:75c677e2f3ea901b762f8b2ab7cf8ad84de915bbea275d66e30b724e23887b1a",
+ "sha256:c3f5a56e107d537e4bb420f7e735841ab2939c8ca6fb528f5fe4124571315b64"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.8'",
+ "version": "==2.1.1"
+ },
+ "packaging": {
+ "hashes": [
+ "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759",
+ "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==24.2"
+ },
+ "pip": {
+ "hashes": [
+ "sha256:3790624780082365f47549d032f3770eeb2b1e8bd1f7b2e02dace1afa361b4ed",
+ "sha256:ebcb60557f2aefabc2e0f918751cd24ea0d56d8ec5445fe1807f1d2109660b99"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==24.3.1"
+ },
+ "pipdeptree": {
+ "hashes": [
+ "sha256:97a455ee53cfa3dfe07223a985e4d473ac96a8b9e953d7db7f27a5e893865023",
+ "sha256:d520e165535e217dd8958dfc14f1922efa0f6e4ff16126a61edb7ed6c538a930"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.8'",
+ "version": "==2.24.0"
+ },
+ "pony": {
+ "hashes": [
+ "sha256:5112b4cf40d3f24e93ae66dc5ab7dc6813388efa870e750928d60dc699873cf5",
+ "sha256:f7f83b2981893e49f7f18e8def52ad8fa8f8e6c5f9583b9aaed62d4d85036a0f"
+ ],
+ "index": "pypi",
+ "version": "==0.7.19"
+ },
+ "psycopg2": {
+ "hashes": [
+ "sha256:0435034157049f6846e95103bd8f5a668788dd913a7c30162ca9503fdf542cb4",
+ "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11",
+ "sha256:47c4f9875125344f4c2b870e41b6aad585901318068acd01de93f3677a6522c2",
+ "sha256:4a579d6243da40a7b3182e0430493dbd55950c493d8c68f4eec0b302f6bbf20e",
+ "sha256:5df2b672140f95adb453af93a7d669d7a7bf0a56bcd26f1502329166f4a61716",
+ "sha256:65a63d7ab0e067e2cdb3cf266de39663203d38d6a8ed97f5ca0cb315c73fe067",
+ "sha256:88138c8dedcbfa96408023ea2b0c369eda40fe5d75002c0964c78f46f11fa442",
+ "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2",
+ "sha256:9d5b3b94b79a844a986d029eee38998232451119ad653aea42bb9220a8c5066b",
+ "sha256:c6f7b8561225f9e711a9c47087388a97fdc948211c10a4bccbf0ba68ab7b3b5a"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.8'",
+ "version": "==2.9.10"
+ },
+ "pyserial": {
+ "hashes": [
+ "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb",
+ "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"
+ ],
+ "index": "pypi",
+ "version": "==3.5"
+ },
+ "python-engineio": {
+ "hashes": [
+ "sha256:145bb0daceb904b4bb2d3eb2d93f7dbb7bb87a6a0c4f20a94cc8654dec977129",
+ "sha256:f0971ac4c65accc489154fe12efd88f53ca8caf04754c46a66e85f5102ef22ad"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==4.11.2"
+ },
+ "python-socketio": {
+ "hashes": [
+ "sha256:0299ff1f470b676c09c1bfab1dead25405077d227b2c13cf217a34dadc68ba9c",
+ "sha256:24a0ea7cfff0e021eb28c68edbf7914ee4111bdf030b95e4d250c4dc9af7a386"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==5.12.1"
+ },
+ "requests": {
+ "hashes": [
+ "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760",
+ "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.8'",
+ "version": "==2.32.3"
+ },
+ "simple-websocket": {
+ "hashes": [
+ "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c",
+ "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"
+ ],
+ "markers": "python_version >= '3.6'",
+ "version": "==1.1.0"
+ },
+ "simplejson": {
+ "hashes": [
+ "sha256:01c6657485393f2e9b8177c77a7634f13ebe70d5e6de150aae1677d91516ce6b",
+ "sha256:0552eb06e7234da892e1d02365cd2b7b2b1f8233aa5aabdb2981587b7cc92ea0",
+ "sha256:06662392e4913dc8846d6a71a6d5de86db5fba244831abe1dd741d62a4136764",
+ "sha256:0733ecd95ae03ae718ec74aad818f5af5f3155d596f7b242acbc1621e765e5fb",
+ "sha256:0766ca6222b410e08e0053a0dda3606cafb3973d5d00538307f631bb59743396",
+ "sha256:0791f64fed7d4abad639491f8a6b1ba56d3c604eb94b50f8697359b92d983f36",
+ "sha256:08f9b443a94e72dd02c87098c96886d35790e79e46b24e67accafbf13b73d43b",
+ "sha256:0959e6cb62e3994b5a40e31047ff97ef5c4138875fae31659bead691bed55896",
+ "sha256:0a32859d45d7b85fb803bb68f6bee14526991a1190269116c33399fa0daf9bbf",
+ "sha256:0b5ddd2c7d1d3f4d23224bc8a04bbf1430ae9a8149c05b90f8fc610f7f857a23",
+ "sha256:0bc5544e3128891bf613b9f71813ee2ec9c11574806f74dd8bb84e5e95bf64a2",
+ "sha256:101a3c8392028cd704a93c7cba8926594e775ca3c91e0bee82144e34190903f1",
+ "sha256:1069143a8fb3905e1bc0696c62be7e3adf812e9f1976ac9ae15b05112ff57cc9",
+ "sha256:1773cabfba66a6337b547e45dafbd471b09487370bcab75bd28f626520410d29",
+ "sha256:1a53a07320c5ff574d8b1a89c937ce33608832f166f39dff0581ac43dc979abd",
+ "sha256:1bd41f2cb1a2c57656ceff67b12d005cb255c728265e222027ad73193a04005a",
+ "sha256:1c49eeb94b8f09dc8a5843c156a22b8bde6aa1ddc65ca8ddc62dddcc001e6a2d",
+ "sha256:1df0aaf1cb787fdf34484ed4a1f0c545efd8811f6028623290fef1a53694e597",
+ "sha256:1e557712fc79f251673aeb3fad3501d7d4da3a27eff0857af2e1d1afbbcf6685",
+ "sha256:1e662336db50ad665777e6548b5076329a94a0c3d4a0472971c588b3ef27de3a",
+ "sha256:212fce86a22188b0c7f53533b0f693ea9605c1a0f02c84c475a30616f55a744d",
+ "sha256:23228037dc5d41c36666384062904d74409a62f52283d9858fa12f4c22cffad1",
+ "sha256:23833ee7e791ec968b744dfee2a2d39df7152050051096caf4296506d75608d8",
+ "sha256:256e09d0f94d9c3d177d9e95fd27a68c875a4baa2046633df387b86b652f5747",
+ "sha256:2876027ebdd599d730d36464debe84619b0368e9a642ca6e7c601be55aed439e",
+ "sha256:2a6a750d3c7461b1c47cfc6bba8d9e57a455e7c5f80057d2a82f738040dd1129",
+ "sha256:2a954b30810988feeabde843e3263bf187697e0eb5037396276db3612434049b",
+ "sha256:2b737a5fefedb8333fa50b8db3dcc9b1d18fd6c598f89fa7debff8b46bf4e511",
+ "sha256:2c78293470313aefa9cfc5e3f75ca0635721fb016fb1121c1c5b0cb8cc74712a",
+ "sha256:2f56eb03bc9e432bb81adc8ecff2486d39feb371abb442964ffb44f6db23b332",
+ "sha256:32a3ada8f3ea41db35e6d37b86dade03760f804628ec22e4fe775b703d567426",
+ "sha256:37105d1d708365b91165e1a6e505bdecc88637091348cf4b6adcdcb4f5a5fb8b",
+ "sha256:3bbcdc438dc1683b35f7a8dc100960c721f922f9ede8127f63bed7dfded4c64c",
+ "sha256:3dc5c1a85ff388e98ea877042daec3d157b6db0d85bac6ba5498034689793e7e",
+ "sha256:42e5acf80d4d971238d4df97811286a044d720693092b20a56d5e56b7dcc5d09",
+ "sha256:49549e3d81ab4a58424405aa545602674d8c35c20e986b42bb8668e782a94bac",
+ "sha256:49cc4c7b940d43bd12bf87ec63f28cbc4964fc4e12c031cc8cd01650f43eb94e",
+ "sha256:4a0710d1a5e41c4f829caa1572793dd3130c8d65c2b194c24ff29c4c305c26e0",
+ "sha256:4dfa420bb9225dd33b6efdabde7c6a671b51150b9b1d9c4e5cd74d3b420b3fe1",
+ "sha256:50d8b742d74c449c4dcac570d08ce0f21f6a149d2d9cf7652dbf2ba9a1bc729a",
+ "sha256:56134bbafe458a7b21f6fddbf889d36bec6d903718f4430768e3af822f8e27c2",
+ "sha256:5bf6a3b9a7d7191471b464fe38f684df10eb491ec9ea454003edb45a011ab187",
+ "sha256:5d9e8f836688a8fabe6a6b41b334aa550a6823f7b4ac3d3712fc0ad8655be9a8",
+ "sha256:619756f1dd634b5bdf57d9a3914300526c3b348188a765e45b8b08eabef0c94e",
+ "sha256:6300680d83a399be2b8f3b0ef7ef90b35d2a29fe6e9c21438097e0938bbc1564",
+ "sha256:637c4d4b81825c1f4d651e56210bd35b5604034b192b02d2d8f17f7ce8c18f42",
+ "sha256:66a0399e21c2112acacfebf3d832ebe2884f823b1c7e6d1363f2944f1db31a99",
+ "sha256:67a20641afebf4cfbcff50061f07daad1eace6e7b31d7622b6fa2c40d43900ba",
+ "sha256:6890ff9cf0bd2e1d487e2a8869ebd620a44684c0a9667fa5ee751d099d5d84c8",
+ "sha256:6d43e24b88c80f997081503f693be832fc90854f278df277dd54f8a4c847ab61",
+ "sha256:6ef9383c5e05f445be60f1735c1816163c874c0b1ede8bb4390aff2ced34f333",
+ "sha256:6f455672f4738b0f47183c5896e3606cd65c9ddee3805a4d18e8c96aa3f47c84",
+ "sha256:6fea0716c593dabb4392c4996d4e902a83b2428e6da82938cf28a523a11eb277",
+ "sha256:7017329ca8d4dca94ad5e59f496e5fc77630aecfc39df381ffc1d37fb6b25832",
+ "sha256:7137e69c6781ecf23afab064be94a277236c9cba31aa48ff1a0ec3995c69171e",
+ "sha256:72e8abbc86fcac83629a030888b45fed3a404d54161118be52cb491cd6975d3e",
+ "sha256:7355c7203353c36d46c4e7b6055293b3d2be097bbc5e2874a2b8a7259f0325dd",
+ "sha256:76f8c28fe2d426182405b18ddf3001fce47835a557dc15c3d8bdea01c03361da",
+ "sha256:7923878b7a0142d39763ec2dbecff3053c1bedd3653585a8474666e420fe83f5",
+ "sha256:7a7bfad839c624e139a4863007233a3f194e7c51551081f9789cba52e4da5167",
+ "sha256:7b5c472099b39b274dcde27f1113db8d818c9aa3ba8f78cbb8ad04a4c1ac2118",
+ "sha256:7c0104b4b7d2c75ccedbf1d9d5a3bd2daa75e51053935a44ba012e2fd4c43752",
+ "sha256:7e062767ac165df9a46963f5735aa4eee0089ec1e48b3f2ec46182754b96f55e",
+ "sha256:7e2a098c21ad8924076a12b6c178965d88a0ad75d1de67e1afa0a66878f277a5",
+ "sha256:817abad79241ed4a507b3caf4d3f2be5079f39d35d4c550a061988986bffd2ec",
+ "sha256:83c87706265ae3028e8460d08b05f30254c569772e859e5ba61fe8af2c883468",
+ "sha256:89b35433186e977fa86ff1fd179c1fadff39cfa3afa1648dab0b6ca53153acd9",
+ "sha256:8e086896c36210ab6050f2f9f095a5f1e03c83fa0e7f296d6cba425411364680",
+ "sha256:8f41bb5370b34f63171e65fdb00e12be1d83675cecb23e627df26f4c88dfc021",
+ "sha256:934a50a614fb831614db5dbfba35127ee277624dda4d15895c957d2f5d48610c",
+ "sha256:93be280fc69a952c76e261036312c20b910e7fa9e234f1d89bdfe3fa34f8a023",
+ "sha256:951095be8d4451a7182403354c22ec2de3e513e0cc40408b689af08d02611588",
+ "sha256:a0782cb9bf827f0c488b6aa0f2819f618308a3caf2973cfd792e45d631bec4db",
+ "sha256:ab69f811a660c362651ae395eba8ce84f84c944cea0df5718ea0ba9d1e4e7252",
+ "sha256:ad0e0b1ce9bd3edb5cf64b5b5b76eacbfdac8c5367153aeeec8a8b1407f68342",
+ "sha256:add8850db04b98507a8b62d248a326ecc8561e6d24336d1ca5c605bbfaab4cad",
+ "sha256:afab2f7f2486a866ff04d6d905e9386ca6a231379181a3838abce1f32fbdcc37",
+ "sha256:b5587feda2b65a79da985ae6d116daf6428bf7489992badc29fc96d16cd27b05",
+ "sha256:b9198c1f1f8910a3b86b60f4fe2556d9d28d3fefe35bffe6be509a27402e694d",
+ "sha256:bc164f32dd9691e7082ce5df24b4cf8c6c394bbf9bdeeb5d843127cd07ab8ad2",
+ "sha256:bcde83a553a96dc7533736c547bddaa35414a2566ab0ecf7d3964fc4bdb84c11",
+ "sha256:c40df31a75de98db2cdfead6074d4449cd009e79f54c1ebe5e5f1f153c68ad20",
+ "sha256:c4f614581b61a26fbbba232a1391f6cee82bc26f2abbb6a0b44a9bba25c56a1c",
+ "sha256:c9bedebdc5fdad48af8783022bae307746d54006b783007d1d3c38e10872a2c6",
+ "sha256:cb324bb903330cbb35d87cce367a12631cd5720afa06e5b9c906483970946da6",
+ "sha256:d00313681015ac498e1736b304446ee6d1c72c5b287cd196996dad84369998f7",
+ "sha256:d0b0efc7279d768db7c74d3d07f0b5c81280d16ae3fb14e9081dc903e8360771",
+ "sha256:d0d5a63f1768fed7e78cf55712dee81f5a345e34d34224f3507ebf71df2b754d",
+ "sha256:d1b8b4d6379fe55f471914345fe6171d81a18649dacf3248abfc9c349b4442eb",
+ "sha256:d36608557b4dcd7a62c29ad4cd7c5a1720bbf7dc942eff9dc42d2c542a5f042d",
+ "sha256:d43c2d7504eda566c50203cdc9dc043aff6f55f1b7dae0dcd79dfefef9159d1c",
+ "sha256:d73efb03c5b39249c82488a994f0998f9e4399e3d085209d2120503305ba77a8",
+ "sha256:d936ae682d5b878af9d9eb4d8bb1fdd5e41275c8eb59ceddb0aeed857bb264a2",
+ "sha256:dd011fc3c1d88b779645495fdb8189fb318a26981eebcce14109460e062f209b",
+ "sha256:dd5b9b1783e14803e362a558680d88939e830db2466f3fa22df5c9319f8eea94",
+ "sha256:dd6a7dabcc4c32daf601bc45e01b79175dde4b52548becea4f9545b0a4428169",
+ "sha256:dd7230d061e755d60a4d5445bae854afe33444cdb182f3815cff26ac9fb29a15",
+ "sha256:e0d2b00ecbcd1a3c5ea1abc8bb99a26508f758c1759fd01c3be482a3655a176f",
+ "sha256:e1a1452ad5723ff129b081e3c8aa4ba56b8734fee4223355ed7b815a7ece69bc",
+ "sha256:e88abff510dcff903a18d11c2a75f9964e768d99c8d147839913886144b2065e",
+ "sha256:ea7a4a998c87c5674a27089e022110a1a08a7753f21af3baf09efe9915c23c3c",
+ "sha256:eb47ee773ce67476a960e2db4a0a906680c54f662521550828c0cc57d0099426",
+ "sha256:eed8cd98a7b24861da9d3d937f5fbfb6657350c547528a117297fe49e3960667",
+ "sha256:ef28c3b328d29b5e2756903aed888960bc5df39b4c2eab157ae212f70ed5bf74",
+ "sha256:ef59a53be400c1fad2c914b8d74c9d42384fed5174f9321dd021b7017fd40270",
+ "sha256:f39caec26007a2d0efab6b8b1d74873ede9351962707afab622cc2285dd26ed0",
+ "sha256:f8efb03ca77bd7725dfacc9254df00d73e6f43013cf39bd37ef1a8ed0ebb5165",
+ "sha256:fa97278ae6614346b5ca41a45a911f37a3261b57dbe4a00602048652c862c28b",
+ "sha256:fc3dc9fb413fc34c396f52f4c87de18d0bd5023804afa8ab5cc224deeb6a9900",
+ "sha256:ff7bc1bbdaa3e487c9469128bf39408e91f5573901cb852e03af378d3582c52d"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '2.5' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==3.19.3"
+ },
+ "urllib3": {
+ "hashes": [
+ "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df",
+ "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"
+ ],
+ "markers": "python_version >= '3.9'",
+ "version": "==2.3.0"
+ },
+ "werkzeug": {
+ "hashes": [
+ "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e",
+ "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746"
+ ],
+ "markers": "python_version >= '3.9'",
+ "version": "==3.1.3"
+ },
+ "wsproto": {
+ "hashes": [
+ "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065",
+ "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"
+ ],
+ "markers": "python_full_version >= '3.7.0'",
+ "version": "==1.2.0"
+ }
+ },
+ "develop": {
+ "black": {
+ "hashes": [
+ "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f",
+ "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd",
+ "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea",
+ "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981",
+ "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b",
+ "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7",
+ "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8",
+ "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175",
+ "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d",
+ "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392",
+ "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad",
+ "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f",
+ "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f",
+ "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b",
+ "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875",
+ "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3",
+ "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800",
+ "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65",
+ "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2",
+ "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812",
+ "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50",
+ "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"
+ ],
+ "index": "pypi",
+ "markers": "python_version >= '3.9'",
+ "version": "==24.10.0"
+ },
+ "click": {
+ "hashes": [
+ "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2",
+ "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"
+ ],
+ "markers": "python_version >= '3.7'",
+ "version": "==8.1.8"
+ },
+ "mypy-extensions": {
+ "hashes": [
+ "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
+ "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
+ ],
+ "markers": "python_version >= '3.5'",
+ "version": "==1.0.0"
+ },
+ "packaging": {
+ "hashes": [
+ "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759",
+ "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==24.2"
+ },
+ "pathspec": {
+ "hashes": [
+ "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08",
+ "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==0.12.1"
+ },
+ "platformdirs": {
+ "hashes": [
+ "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907",
+ "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"
+ ],
+ "markers": "python_version >= '3.8'",
+ "version": "==4.3.6"
+ }
+ }
+}
diff --git a/src/server/config.py b/src/server/config.py
new file mode 100644
index 0000000..1f85278
--- /dev/null
+++ b/src/server/config.py
@@ -0,0 +1,14 @@
+# from os import environ
+
+
+class ApplicationConfig:
+ """Class for application configuration"""
+
+ # SECRET_KEY = environ.get("SECRET_KEY")
+ SECRET_KEY = "asdfasfasdf4564asdf"
+ SESSION_TYPE = "redis"
+ SESSION_PERMANENT = False
+ SESSION_USE_SIGNER = True
+ # SESSION_COOKIE_SECURE = True
+ SESSION_COOKIE_SAMESITE = "Lax"
+ JSON_SORT_KEYS = False
diff --git a/src/server/main.py b/src/server/main.py
new file mode 100755
index 0000000..d5a83fa
--- /dev/null
+++ b/src/server/main.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+import json
+from signal import signal, SIGINT
+
+from flask import Flask
+from flask_cors import CORS
+from flask_socketio import SocketIO
+
+from config import ApplicationConfig
+from modules.blueprints import blueprints
+from modules.database import init_database
+from modules.handler_manager import HandlerManager
+from modules.utils import Context, ModulesRegistrator, IntListConverter
+
+registered_modules = ModulesRegistrator()
+
+
+def _quit_handler(_, __):
+ """Handler for exit signal."""
+ print("\nSIGINT signal detected. Exiting")
+ _quit()
+
+
+def _quit():
+ registered_modules.exit()
+ quit()
+
+
+if __name__ == "__main__":
+ print("Starting Contwatch...")
+
+ signal(SIGINT, _quit_handler)
+
+ app = Flask(__name__)
+ app.config.from_object(ApplicationConfig)
+ app.url_map.converters["int_list"] = IntListConverter
+ app.url_map.strict_slashes = False
+ cors = CORS(app, supports_credentials=True)
+ socketio = SocketIO(app, cors_allowed_origins="*")
+
+ config = {}
+ # Try to load the configuration from the file
+ try:
+ with open("config.json", "r") as file:
+ config = json.load(file)
+ except FileNotFoundError:
+ print("Configuration file not found. Using default configuration.")
+
+ # Database initialization
+ print("Initializing database...")
+ init_database(config.get("database", {}))
+
+ # HandlerManager initialization
+ manager = HandlerManager(socketio)
+ registered_modules.add(manager)
+
+ # Blueprints registration
+ print("Registering blueprints...")
+ for name, blueprint in blueprints.items():
+ app.register_blueprint(blueprint(Context(manager, socketio)), url_prefix=f"/api/core/{name}")
+
+ print("Starting Socketio server...")
+ socketio.run(app, host="0.0.0.0", debug=True, use_reloader=False, allow_unsafe_werkzeug=True)
diff --git a/modules/__init__.py b/src/server/modules/__init__.py
similarity index 100%
rename from modules/__init__.py
rename to src/server/modules/__init__.py
diff --git a/src/server/modules/actions/__init__.py b/src/server/modules/actions/__init__.py
new file mode 100644
index 0000000..d6588ec
--- /dev/null
+++ b/src/server/modules/actions/__init__.py
@@ -0,0 +1,2 @@
+from .nodes import NODES, NODES_MAP
+from .ports import PORTS
diff --git a/src/server/modules/actions/controls/__init__.py b/src/server/modules/actions/controls/__init__.py
new file mode 100644
index 0000000..8ba734c
--- /dev/null
+++ b/src/server/modules/actions/controls/__init__.py
@@ -0,0 +1,4 @@
+from .checkbox import Checkbox
+from .number import Number
+from .select import Select
+from .text import Text
diff --git a/src/server/modules/actions/controls/abstract_control.py b/src/server/modules/actions/controls/abstract_control.py
new file mode 100644
index 0000000..01d4b97
--- /dev/null
+++ b/src/server/modules/actions/controls/abstract_control.py
@@ -0,0 +1,14 @@
+class AbstractControl:
+ type = None
+ name = None
+ label = None
+
+ def __init__(self):
+ self.type = type(self).__name__.lower()
+
+ def get_definition(self):
+ return {
+ "type": self.type,
+ "name": self.name,
+ "label": self.label,
+ }
diff --git a/src/server/modules/actions/controls/checkbox.py b/src/server/modules/actions/controls/checkbox.py
new file mode 100644
index 0000000..320b5c2
--- /dev/null
+++ b/src/server/modules/actions/controls/checkbox.py
@@ -0,0 +1,6 @@
+from .abstract_control import AbstractControl
+
+
+class Checkbox(AbstractControl):
+ name = "checkbox"
+ label = "Checkbox"
diff --git a/src/server/modules/actions/controls/number.py b/src/server/modules/actions/controls/number.py
new file mode 100644
index 0000000..253aa81
--- /dev/null
+++ b/src/server/modules/actions/controls/number.py
@@ -0,0 +1,6 @@
+from .abstract_control import AbstractControl
+
+
+class Number(AbstractControl):
+ name = "number"
+ label = "Number"
diff --git a/src/server/modules/actions/controls/select.py b/src/server/modules/actions/controls/select.py
new file mode 100644
index 0000000..cddd905
--- /dev/null
+++ b/src/server/modules/actions/controls/select.py
@@ -0,0 +1,24 @@
+from .abstract_control import AbstractControl
+
+
+class Select(AbstractControl):
+ name = "select"
+ label = "Select"
+
+ def __init__(self, options):
+ super().__init__()
+ self.options = sorted(options, key=lambda option: option["value"])
+
+ def get_definition(self):
+ return {
+ "type": self.type,
+ "name": self.name,
+ "label": self.label,
+ "options": [
+ {
+ "label": option["label"],
+ "value": option["value"],
+ }
+ for option in self.options
+ ],
+ }
diff --git a/src/server/modules/actions/controls/text.py b/src/server/modules/actions/controls/text.py
new file mode 100644
index 0000000..94e33f8
--- /dev/null
+++ b/src/server/modules/actions/controls/text.py
@@ -0,0 +1,6 @@
+from .abstract_control import AbstractControl
+
+
+class Text(AbstractControl):
+ name = "text"
+ label = "Text"
diff --git a/src/server/modules/actions/nodes/__init__.py b/src/server/modules/actions/nodes/__init__.py
new file mode 100644
index 0000000..aa0d0b2
--- /dev/null
+++ b/src/server/modules/actions/nodes/__init__.py
@@ -0,0 +1,23 @@
+from .action_performer import ActionPerformer
+from .aggregator import Aggregator
+from .attribute_reader import AttributeReader
+from .attribute_reader_listener import AttributeReaderListener
+from .condition import Condition
+from .evaluator import Evaluator
+from .handler_listener import HandlerListener
+from .logger import Logger
+from .negation import Negation
+
+NODES = [
+ ActionPerformer,
+ Aggregator,
+ AttributeReaderListener,
+ AttributeReader,
+ Condition,
+ Evaluator,
+ HandlerListener,
+ Logger,
+ Negation,
+]
+
+NODES_MAP = {node.__name__: node for node in NODES}
diff --git a/src/server/modules/actions/nodes/abstract_node.py b/src/server/modules/actions/nodes/abstract_node.py
new file mode 100644
index 0000000..12e8cd7
--- /dev/null
+++ b/src/server/modules/actions/nodes/abstract_node.py
@@ -0,0 +1,79 @@
+class AbstractNode:
+ type = None
+ label = None
+ description = None
+ repeatable_input = ""
+
+ def __init__(self, context, node_settings=None):
+ self.type = type(self).__name__
+ self.context = context
+
+ self.input_ports = []
+ self.output_ports = []
+ self.node_settings = {}
+ self.input_connections = {}
+ self.output_connections = {}
+
+ if node_settings:
+ self.load_node_settings(node_settings)
+
+ def get_definition(self):
+ """Serialize the node to a dictionary."""
+ return {
+ "type": self.type,
+ "label": self.label,
+ "description": self.description,
+ "repeatableInput": self.repeatable_input,
+ "inputs": [input_port.get_definition() for input_port in self.input_ports],
+ "outputs": [output_port.get_definition() for output_port in self.output_ports],
+ }
+
+ def get_input(self, name):
+ """Get the value of an input."""
+ for connection in self.input_connections.get(name, []):
+ return connection.evaluate()
+ for port in self.input_ports:
+ if port.name == name:
+ return self.node_settings.get("inputData", {}).get(port.name, {}).get(port.controls[0].name)
+
+ def get_repeatable_inputs(self, name):
+ """Get all values of a repeatable input."""
+ result = []
+ for connection_name in self.input_connections:
+ if connection_name.startswith(name):
+ result.append(self.get_input(connection_name))
+ for port_name in self.node_settings.get("inputData", {}):
+ if port_name.startswith(name):
+ repeatable_ports = list(filter(lambda port: port.name == name, self.input_ports))
+ if not repeatable_ports:
+ continue
+ value = (
+ self.node_settings.get("inputData", {}).get(port_name, {}).get(repeatable_ports[0].controls[0].name)
+ )
+ if value:
+ result.append(value)
+ return result
+
+ def load_node_settings(self, node_settings):
+ """Load node settings from a dictionary provided by Flume."""
+ self.node_settings = node_settings
+
+ def add_input_connection(self, port, connection):
+ """Add an input connection to the node."""
+ if not self.input_connections.get(port):
+ self.input_connections[port] = []
+ self.input_connections[port].append(connection)
+
+ def add_output_connection(self, port, connection):
+ """Add an output connection to the node."""
+ if not self.output_connections.get(port):
+ self.output_connections[port] = []
+ self.output_connections[port].append(connection)
+
+ def execute(self):
+ """Execute the node."""
+ raise NotImplementedError()
+
+ def evaluate(self):
+ """Evaluate the node."""
+ raise NotImplementedError()
diff --git a/src/server/modules/actions/nodes/action_performer.py b/src/server/modules/actions/nodes/action_performer.py
new file mode 100644
index 0000000..1e045c9
--- /dev/null
+++ b/src/server/modules/actions/nodes/action_performer.py
@@ -0,0 +1,25 @@
+from pony import orm
+
+from modules.models.action import Action as ActionModel
+from .abstract_node import AbstractNode
+from ..ports import Action, Event, Handler
+
+
+class ActionPerformer(AbstractNode):
+ label = "Action Performer"
+ description = "Performs an action"
+
+ def __init__(self, context, node_settings=None):
+ super().__init__(context, node_settings)
+ self.input_ports = [Event(context), Handler(context), Action(context)]
+
+ @orm.db_session
+ def execute(self):
+ print("Performing action")
+ handler_id = self.get_input("Handler")
+ action_id = self.get_input("Action")
+ if handler_id and action_id:
+ handler = self.context.manager.registered_handlers.get(handler_id)
+ action = ActionModel.select(lambda a: a.id == action_id).first()
+ if handler and action:
+ handler.send_message({"label": action.message})
diff --git a/src/server/modules/actions/nodes/aggregator.py b/src/server/modules/actions/nodes/aggregator.py
new file mode 100644
index 0000000..2106e60
--- /dev/null
+++ b/src/server/modules/actions/nodes/aggregator.py
@@ -0,0 +1,25 @@
+from .abstract_node import AbstractNode
+from ..ports import Value, AggregateFunction
+from ..ports.utils import AGGREGATE_FUNCTIONS
+
+
+class Aggregator(AbstractNode):
+ label = "Aggregator"
+ description = "Aggregates input values and returns value"
+ repeatable_input = "Value"
+
+ def __init__(self, context, node_settings=None):
+ super().__init__(context, node_settings)
+ self.input_ports = [
+ AggregateFunction(context),
+ Value(context),
+ ]
+ self.output_ports = [Value(context)]
+
+ def evaluate(self):
+ try:
+ result = map(lambda x: float(x), self.get_repeatable_inputs("Value"))
+ return AGGREGATE_FUNCTIONS[self.get_input("AggregateFunction")](*result)
+ except ValueError as error:
+ print(error)
+ return None
diff --git a/src/server/modules/actions/nodes/attribute_reader.py b/src/server/modules/actions/nodes/attribute_reader.py
new file mode 100644
index 0000000..2f2894e
--- /dev/null
+++ b/src/server/modules/actions/nodes/attribute_reader.py
@@ -0,0 +1,24 @@
+from pony import orm
+
+from .abstract_node import AbstractNode
+from ..ports import Attribute, Value
+
+
+class AttributeReader(AbstractNode):
+ label = "Attribute Reader"
+ description = "Reads an attribute from a handler"
+
+ def __init__(self, context, node_settings=None):
+ super().__init__(context, node_settings)
+ self.input_ports = [Attribute(context)]
+ self.output_ports = [Value(context)]
+
+ @orm.db_session
+ def evaluate(self):
+ attribute_id = self.get_input("Attribute")
+ if attribute_id:
+ for attributes in self.context.manager.registered_attributes.values():
+ for attribute in attributes.values():
+ if attribute.get_id() == attribute_id:
+ return attribute.get_current_value()
+ return None
diff --git a/src/server/modules/actions/nodes/attribute_reader_listener.py b/src/server/modules/actions/nodes/attribute_reader_listener.py
new file mode 100644
index 0000000..8d00154
--- /dev/null
+++ b/src/server/modules/actions/nodes/attribute_reader_listener.py
@@ -0,0 +1,15 @@
+from . import AttributeReader
+from ..ports import Event, Value
+
+
+class AttributeReaderListener(AttributeReader):
+ label = "Attribute Change Listener"
+ description = "Triggers when attribute value changes"
+
+ def __init__(self, context, node_settings=None):
+ super().__init__(context, node_settings)
+ self.output_ports = [Event(context), Value(context)]
+
+ def execute(self):
+ for connection in self.output_connections.get("Event", []):
+ connection.execute()
diff --git a/src/server/modules/actions/nodes/condition.py b/src/server/modules/actions/nodes/condition.py
new file mode 100644
index 0000000..4824719
--- /dev/null
+++ b/src/server/modules/actions/nodes/condition.py
@@ -0,0 +1,23 @@
+from .abstract_node import AbstractNode
+from ..ports import Event, Value
+
+
+class Condition(AbstractNode):
+ label = "Condition"
+ description = "Triggers event on according branch based on a input value"
+
+ def __init__(self, context, node_settings=None):
+ super().__init__(context, node_settings)
+ self.input_ports = [Event(context), Value(context)]
+ self.output_ports = [
+ Event(context, "TrueEvent", "True Event"),
+ Event(context, "FalseEvent", "False Event"),
+ ]
+
+ def execute(self):
+ if self.get_input("Value"):
+ for connection in self.output_connections.get("TrueEvent", []):
+ connection.execute()
+ else:
+ for connection in self.output_connections.get("FalseEvent", []):
+ connection.execute()
diff --git a/src/server/modules/actions/nodes/evaluator.py b/src/server/modules/actions/nodes/evaluator.py
new file mode 100644
index 0000000..b07ce99
--- /dev/null
+++ b/src/server/modules/actions/nodes/evaluator.py
@@ -0,0 +1,31 @@
+from .abstract_node import AbstractNode
+from ..ports import RelationalOperator, Value
+from ..ports.utils import RELATIONAL_OPERATIONS
+
+
+class Evaluator(AbstractNode):
+ label = "Evaluator"
+ description = "Evaluates a condition and returns value"
+
+ def __init__(self, context, node_settings=None):
+ super().__init__(context, node_settings)
+ self.input_ports = [
+ Value(context, "FirstValue"),
+ RelationalOperator(context, "Condition"),
+ Value(context, "SecondValue"),
+ ]
+ self.output_ports = [Value(context)]
+
+ def evaluate(self):
+ condition = self.get_input("Condition")
+ try:
+ first_value = float(self.get_input("FirstValue")) if self.get_input("FirstValue") else None
+ second_value = float(self.get_input("SecondValue")) if self.get_input("SecondValue") else None
+ return (
+ RELATIONAL_OPERATIONS[condition](first_value, second_value)
+ if first_value is not None and second_value is not None
+ else None
+ )
+ except ValueError as error:
+ print(error)
+ return False
diff --git a/src/server/modules/actions/nodes/handler_listener.py b/src/server/modules/actions/nodes/handler_listener.py
new file mode 100644
index 0000000..32c68e6
--- /dev/null
+++ b/src/server/modules/actions/nodes/handler_listener.py
@@ -0,0 +1,17 @@
+from .abstract_node import AbstractNode
+from ..ports import Event, Handler
+
+
+class HandlerListener(AbstractNode):
+ label = "Handler Listener"
+ description = "Triggers when handler data is received"
+
+ def __init__(self, context, node_settings=None):
+ super().__init__(context, node_settings)
+ self.input_ports = [Handler(context)]
+ self.output_ports = [Event(context)]
+
+ def execute(self):
+ """Execute the node."""
+ for connection in self.output_connections.get("Event", []):
+ connection.execute()
diff --git a/src/server/modules/actions/nodes/logger.py b/src/server/modules/actions/nodes/logger.py
new file mode 100644
index 0000000..3f6b5d6
--- /dev/null
+++ b/src/server/modules/actions/nodes/logger.py
@@ -0,0 +1,15 @@
+from .abstract_node import AbstractNode
+from ..ports import Value, Event
+
+
+class Logger(AbstractNode):
+ label = "Logger"
+ description = "Logs value to console and system log"
+
+ def __init__(self, context, node_settings=None):
+ super().__init__(context, node_settings)
+ self.input_ports = [Event(context), Value(context)]
+
+ def execute(self):
+ value = self.get_input("Value")
+ print(value)
diff --git a/src/server/modules/actions/nodes/negation.py b/src/server/modules/actions/nodes/negation.py
new file mode 100644
index 0000000..d23e8d6
--- /dev/null
+++ b/src/server/modules/actions/nodes/negation.py
@@ -0,0 +1,16 @@
+from .abstract_node import AbstractNode
+from ..ports import Value
+
+
+class Negation(AbstractNode):
+ label = "Negation"
+ description = "Negates an input value"
+
+ def __init__(self, context, node_settings=None):
+ super().__init__(context, node_settings)
+ self.input_ports = [Value(context)]
+ self.output_ports = [Value(context)]
+
+ def evaluate(self):
+ """Return the negated value."""
+ return not self.get_input("Value")
diff --git a/src/server/modules/actions/ports/__init__.py b/src/server/modules/actions/ports/__init__.py
new file mode 100644
index 0000000..eecb5ab
--- /dev/null
+++ b/src/server/modules/actions/ports/__init__.py
@@ -0,0 +1,19 @@
+from .action import Action
+from .aggregate_function import AggregateFunction
+from .attribute import Attribute
+from .event import Event
+from .handler import Handler
+from .number import Number
+from .relational_operator import RelationalOperator
+from .value import Value
+
+PORTS = [
+ Action,
+ AggregateFunction,
+ Attribute,
+ Event,
+ Handler,
+ Number,
+ RelationalOperator,
+ Value,
+]
diff --git a/src/server/modules/actions/ports/abstract_port.py b/src/server/modules/actions/ports/abstract_port.py
new file mode 100644
index 0000000..f0af98b
--- /dev/null
+++ b/src/server/modules/actions/ports/abstract_port.py
@@ -0,0 +1,23 @@
+class AbstractPort:
+ type = None
+ name = None
+ label = None
+ color = None
+ hide = False
+ controls = []
+
+ def __init__(self, context, name=None, label=None):
+ self.type = type(self).__name__
+ self.context = context
+ self.name = name or self.type
+ self.label = label or self.label
+
+ def get_definition(self):
+ return {
+ "type": self.type,
+ "name": self.name,
+ "label": self.label,
+ "hidePort": self.hide,
+ "color": self.color,
+ "controls": [control.get_definition() for control in self.controls],
+ }
diff --git a/src/server/modules/actions/ports/action.py b/src/server/modules/actions/ports/action.py
new file mode 100644
index 0000000..1987270
--- /dev/null
+++ b/src/server/modules/actions/ports/action.py
@@ -0,0 +1,25 @@
+from pony import orm
+
+from modules.models.action import Action as ActionModel
+from .abstract_port import AbstractPort
+from ..controls import Select
+
+
+class Action(AbstractPort):
+ label = "Action"
+ hide = True
+
+ @orm.db_session
+ def __init__(self, context, *args):
+ super().__init__(context, *args)
+ self.controls = [
+ Select(
+ [
+ {
+ "label": action.name,
+ "value": action.id,
+ }
+ for action in ActionModel.select()
+ ]
+ )
+ ]
diff --git a/src/server/modules/actions/ports/aggregate_function.py b/src/server/modules/actions/ports/aggregate_function.py
new file mode 100644
index 0000000..910cabf
--- /dev/null
+++ b/src/server/modules/actions/ports/aggregate_function.py
@@ -0,0 +1,22 @@
+from .abstract_port import AbstractPort
+from .utils.aggregate_functions import AGGREGATE_FUNCTIONS
+from ..controls import Select
+
+
+class AggregateFunction(AbstractPort):
+ label = "Function"
+ hide = True
+
+ def __init__(self, context, *args):
+ super().__init__(context, *args)
+ self.controls = [
+ Select(
+ [
+ {
+ "label": function,
+ "value": function,
+ }
+ for function in AGGREGATE_FUNCTIONS
+ ]
+ )
+ ]
diff --git a/src/server/modules/actions/ports/attribute.py b/src/server/modules/actions/ports/attribute.py
new file mode 100644
index 0000000..f88d483
--- /dev/null
+++ b/src/server/modules/actions/ports/attribute.py
@@ -0,0 +1,27 @@
+from .abstract_port import AbstractPort
+from ..controls import Select
+
+
+class Attribute(AbstractPort):
+ label = "Attribute"
+ hide = True
+
+ def __init__(self, context, *args):
+ super().__init__(context, *args)
+ self.controls = [
+ Select(
+ sum(
+ (
+ [
+ {
+ "label": attribute.get_name(),
+ "value": attribute.get_id(),
+ }
+ for attribute_name, attribute in attributes.items()
+ ]
+ for handler_id, attributes in self.context.manager.registered_attributes.copy().items()
+ ),
+ [],
+ ),
+ )
+ ]
diff --git a/src/server/modules/actions/ports/event.py b/src/server/modules/actions/ports/event.py
new file mode 100644
index 0000000..8ab8828
--- /dev/null
+++ b/src/server/modules/actions/ports/event.py
@@ -0,0 +1,7 @@
+from .abstract_port import AbstractPort
+from .utils import Color
+
+
+class Event(AbstractPort):
+ label = "Event"
+ color = Color.YELLOW
diff --git a/src/server/modules/actions/ports/handler.py b/src/server/modules/actions/ports/handler.py
new file mode 100644
index 0000000..206c586
--- /dev/null
+++ b/src/server/modules/actions/ports/handler.py
@@ -0,0 +1,21 @@
+from .abstract_port import AbstractPort
+from ..controls import Select
+
+
+class Handler(AbstractPort):
+ label = "Handler"
+ hide = True
+
+ def __init__(self, context, *args):
+ super().__init__(context, *args)
+ self.controls = [
+ Select(
+ [
+ {
+ "label": handler.get_name(),
+ "value": handler_id,
+ }
+ for handler_id, handler in self.context.manager.registered_handlers.items()
+ ]
+ )
+ ]
diff --git a/src/server/modules/actions/ports/number.py b/src/server/modules/actions/ports/number.py
new file mode 100644
index 0000000..8ea79e2
--- /dev/null
+++ b/src/server/modules/actions/ports/number.py
@@ -0,0 +1,8 @@
+from .abstract_port import AbstractPort
+from ..controls import Number as NumberControl
+
+
+class Number(AbstractPort):
+ label = "Number"
+
+ controls = [NumberControl()]
diff --git a/src/server/modules/actions/ports/relational_operator.py b/src/server/modules/actions/ports/relational_operator.py
new file mode 100644
index 0000000..0643c63
--- /dev/null
+++ b/src/server/modules/actions/ports/relational_operator.py
@@ -0,0 +1,22 @@
+from .abstract_port import AbstractPort
+from .utils import RELATIONAL_OPERATIONS
+from ..controls import Select
+
+
+class RelationalOperator(AbstractPort):
+ label = "Operator"
+ hide = True
+
+ def __init__(self, *args):
+ super().__init__(*args)
+ self.controls = [
+ Select(
+ [
+ {
+ "label": operator,
+ "value": operator,
+ }
+ for operator in RELATIONAL_OPERATIONS
+ ]
+ )
+ ]
diff --git a/src/server/modules/actions/ports/utils/__init__.py b/src/server/modules/actions/ports/utils/__init__.py
new file mode 100644
index 0000000..8919268
--- /dev/null
+++ b/src/server/modules/actions/ports/utils/__init__.py
@@ -0,0 +1,3 @@
+from .aggregate_functions import AGGREGATE_FUNCTIONS
+from .color import Color
+from .relational_operations import RELATIONAL_OPERATIONS
diff --git a/src/server/modules/actions/ports/utils/aggregate_functions.py b/src/server/modules/actions/ports/utils/aggregate_functions.py
new file mode 100644
index 0000000..045f240
--- /dev/null
+++ b/src/server/modules/actions/ports/utils/aggregate_functions.py
@@ -0,0 +1,7 @@
+AGGREGATE_FUNCTIONS = {
+ "Minimum": lambda *args: min(args),
+ "Maximum": lambda *args: max(args),
+ "Summation": lambda *args: sum(args),
+ "Average": lambda *args: sum(args) / len(args),
+ "Median": lambda *args: sorted(args)[len(args) // 2],
+}
diff --git a/src/server/modules/actions/ports/utils/color.py b/src/server/modules/actions/ports/utils/color.py
new file mode 100644
index 0000000..c643d8e
--- /dev/null
+++ b/src/server/modules/actions/ports/utils/color.py
@@ -0,0 +1,11 @@
+class Color:
+ """Flume color constants. https://flume.dev/docs/colors"""
+
+ YELLOW = "yellow"
+ ORANGE = "orange"
+ RED = "red"
+ PINK = "pink"
+ PURPLE = "purple"
+ BLUE = "blue"
+ GREEN = "green"
+ GRAY = "gray"
diff --git a/src/server/modules/actions/ports/utils/relational_operations.py b/src/server/modules/actions/ports/utils/relational_operations.py
new file mode 100644
index 0000000..fe0bfa9
--- /dev/null
+++ b/src/server/modules/actions/ports/utils/relational_operations.py
@@ -0,0 +1,8 @@
+RELATIONAL_OPERATIONS = {
+ ">=": lambda le, ri: le >= ri,
+ "<=": lambda le, ri: le <= ri,
+ ">": lambda le, ri: le > ri,
+ "<": lambda le, ri: le < ri,
+ "==": lambda le, ri: le == ri,
+ "<>": lambda le, ri: le != ri,
+}
diff --git a/src/server/modules/actions/ports/value.py b/src/server/modules/actions/ports/value.py
new file mode 100644
index 0000000..546451e
--- /dev/null
+++ b/src/server/modules/actions/ports/value.py
@@ -0,0 +1,10 @@
+from .abstract_port import AbstractPort
+from .utils import Color
+from ..controls import Text
+
+
+class Value(AbstractPort):
+ label = "Value"
+ color = Color.BLUE
+
+ controls = [Text()]
diff --git a/src/server/modules/attribute_manager.py b/src/server/modules/attribute_manager.py
new file mode 100644
index 0000000..0d970e1
--- /dev/null
+++ b/src/server/modules/attribute_manager.py
@@ -0,0 +1,177 @@
+from collections import deque
+from datetime import datetime
+
+from pony import orm
+
+from modules.models import attribute as attribute_model
+from modules.models import data_stat as data_stat_model
+from modules.models import data_unit as data_unit_model
+from modules.models import unit as unit_model
+
+
+class AttributeManager:
+ """Handles work with attributes and data"""
+
+ def __init__(self, db_instance, socketio):
+ print(f"Initializing AttributeManager {db_instance.name}...")
+ self.id = db_instance.id
+ self.name = db_instance.name
+ self.id = db_instance.id
+ self.handler_id = db_instance.handler.id
+ self.name = db_instance.name
+ self.label = db_instance.label
+ self.rounding = db_instance.rounding
+ self.socketio = socketio
+ self.value = None
+ self.display_value = None
+ self.base_unit = db_instance.unit
+ self.display_unit = None
+ self.last_value_save_skipped = False
+ self.last_date = datetime.now().date()
+
+ # print("Fetching last values from DB...")
+ # # Use raw SQL to bypass all ORM overhead and object mapping
+ # # This is the "nuclear option" for performance
+ # try:
+ # # We select only the 'value' column from the 'DataUnit' table
+ # # Filtering by attribute ID and date, ordering by ID descending
+ # raw_results = db_instance._database_.select(
+ # "value FROM DataUnit WHERE attribute = $attr_id AND date = $target_date "
+ # "ORDER BY id DESC LIMIT 6",
+ # {"attr_id": self.id, "target_date": self.last_date}
+ # )
+ # last_values = list(raw_results)[::-1]
+ # except Exception as e:
+ # print(f"Raw SQL failed: {e}. Falling back to empty list.")
+ # last_values = []
+ #
+ # print(f"Last values for AttributeManager {db_instance.name}: {last_values}")
+ self.trend_queue = deque(maxlen=3)
+
+ # last_added = None
+ # for value in last_values:
+ # if last_added != value and value is not None:
+ # self.trend_queue.append(value)
+ # last_added = value
+ # self.value = value
+
+ self.last_datetime = None
+
+ self.stats = {}
+ self.init_stats()
+
+ self.stat_predicates = {
+ "max": lambda val: val > self.stats["max"],
+ "min": lambda val: val < self.stats["min"],
+ }
+
+ def init_stats(self):
+ self.stats = {
+ "max": None,
+ "min": None,
+ }
+
+ def refresh_config(self, db_instance):
+ self.label = db_instance.label
+ self.rounding = db_instance.rounding
+
+ def get_id(self):
+ return self.id
+
+ def get_name(self):
+ return self.name
+
+ def get_current_value(self):
+ return self.value
+
+ def get_display_value(self):
+ return self.display_value or self.value
+
+ def get_display_unit(self):
+ return self.display_unit or self.base_unit
+
+ def get_instance(self):
+ return attribute_model.get_by_id(self.id)
+
+ def get_trend(self):
+ if len(self.trend_queue) < 3:
+ return 0
+ if self.trend_queue[0] < self.trend_queue[1] < self.trend_queue[2]:
+ return 1
+ if self.trend_queue[0] > self.trend_queue[1] > self.trend_queue[2]:
+ return -1
+ return 0
+
+ def check_value_change(self, value):
+ return value != self.value
+
+ @orm.db_session
+ def add_data_unit(self, value):
+ value_changed = False
+ # TODO: We need to create two new data units when the day changes.
+ value = round(value, self.rounding if self.rounding is not None else 2)
+
+ if self.check_value_change(value):
+ # Value has changed
+ if self.value is not None and self.last_datetime is not None and self.last_value_save_skipped:
+ data_unit_model.add(self.handler_id, self.id, self.value, self.last_datetime)
+ self.last_value_save_skipped = False
+ self.value = value
+ data_unit_model.add(self.handler_id, self.id, value, datetime.now())
+ self.trend_queue.append(self.value)
+
+ # TODO: Make this more dynamic
+ if len(str(abs(int(value)))) > 3:
+ unit = unit_model.Unit.select(lambda u: u.base_unit == self.base_unit and u.base_ratio == 0.001).first()
+ if unit:
+ self.display_value = round(
+ value
+ * unit_model.Unit.select(lambda u: u.base_unit == self.base_unit and u.base_ratio == 0.001)
+ .first()
+ .base_ratio,
+ 2,
+ )
+ self.display_unit = (
+ unit_model.Unit.select(lambda u: u.base_unit == self.base_unit and u.base_ratio == 0.001)
+ .first()
+ .name
+ )
+ else:
+ self.display_value = value
+ self.display_unit = self.get_instance().unit
+ else:
+ self.display_value = value
+ self.display_unit = self.get_instance().unit
+
+ self.check_and_add_stat_units(value)
+ value_changed = True
+ else:
+ # Value hasn't changed
+ self.last_value_save_skipped = True
+ self.last_datetime = datetime.now()
+ return value_changed
+
+ def check_and_add_stat_units(self, value):
+ now = datetime.now()
+ if now.date() > self.last_date:
+ # New day has started, reset stats
+ self.init_stats()
+ self.last_date = now.date()
+ for predicate_name, stat_predicate in self.stat_predicates.items():
+ if self.stats[predicate_name] is None:
+ # If stat is not found, it may not be loaded from DB yet. Try to load it.
+ db_stat = data_stat_model.get_by_type_and_date(self.id, predicate_name, now.date())
+ self.stats[predicate_name] = db_stat.value if db_stat else None
+
+ if self.stats[predicate_name] is not None and stat_predicate(value):
+ # If stat is found in db and predicate is true, update stat in db.
+ db_stat = data_stat_model.get_by_type_and_date(self.id, predicate_name, now.date())
+ db_stat.time = now.time()
+ db_stat.value = value
+ self.stats[predicate_name] = value
+ self.socketio.emit("mutate", f"core/data-stats?attribute={self.id}")
+ elif self.stats[predicate_name] is None:
+ # If stat is still not in db, add it.
+ data_stat_model.add(self.handler_id, self.id, predicate_name, value)
+ self.stats[predicate_name] = value
+ self.socketio.emit("mutate", f"core/data-stats?attribute={self.id}")
diff --git a/src/server/modules/blueprints/__init__.py b/src/server/modules/blueprints/__init__.py
new file mode 100644
index 0000000..8d85df9
--- /dev/null
+++ b/src/server/modules/blueprints/__init__.py
@@ -0,0 +1,15 @@
+from .actions import actions_blueprint
+from .attributes import attributes_blueprint
+from .charts import charts_blueprint
+from .datastats import datastats_blueprint
+from .handlers import handlers_blueprint
+from .widgets import widgets_blueprint
+
+blueprints = {
+ "actions": actions_blueprint,
+ "attributes": attributes_blueprint,
+ "charts": charts_blueprint,
+ "data-stats": datastats_blueprint,
+ "handlers": handlers_blueprint,
+ "widgets": widgets_blueprint,
+}
diff --git a/src/server/modules/blueprints/actions.py b/src/server/modules/blueprints/actions.py
new file mode 100644
index 0000000..03e72a4
--- /dev/null
+++ b/src/server/modules/blueprints/actions.py
@@ -0,0 +1,39 @@
+from flask import Blueprint, request
+from pony import orm
+
+from modules.actions import NODES, PORTS
+from modules.models.settings import set_settings, get_settings
+from modules.utils import Context, this_name, StatusCode
+
+
+def actions_blueprint(_context: Context):
+ blueprint = Blueprint(this_name(), __name__)
+
+ @blueprint.route("/available-ports")
+ def ports():
+ return [port(_context).get_definition() for port in PORTS], StatusCode.OK
+
+ @blueprint.route("/available-nodes")
+ def nodes():
+ return [node(_context).get_definition() for node in NODES], StatusCode.OK
+
+ @blueprint.route("/node-map")
+ @orm.db_session
+ def node_map():
+ settings = get_settings(1)
+ if settings:
+ return settings.actions_node_map, StatusCode.OK
+ else:
+ return {}, StatusCode.NOT_FOUND
+
+ @blueprint.route("/save-node-map", methods=["POST"])
+ @orm.db_session
+ def save_node_map():
+ actions_node_map = request.json
+ if (not actions_node_map) or (not isinstance(actions_node_map, dict)):
+ return {"status": "error", "message": "Invalid request body"}, StatusCode.BAD_REQUEST
+ _context.manager.set_actions_node_map(actions_node_map)
+ set_settings(1, actions_node_map)
+ return {"status": "ok"}, StatusCode.OK
+
+ return blueprint
diff --git a/src/server/modules/blueprints/attributes.py b/src/server/modules/blueprints/attributes.py
new file mode 100644
index 0000000..7798cac
--- /dev/null
+++ b/src/server/modules/blueprints/attributes.py
@@ -0,0 +1,81 @@
+from flask import Blueprint, request
+from pony import orm
+
+from modules.models.attribute import Attribute
+from modules.utils import Context, this_name, StatusCode
+
+
+def attributes_blueprint(_context: Context):
+ blueprint = Blueprint(this_name(), __name__)
+
+ def registered_attribute(attribute):
+ return _context.manager.registered_attributes.get(attribute.handler.id, {}).get(attribute.name)
+
+ def attribute_serializer(attribute):
+ return {
+ "id": attribute.id,
+ "name": attribute.name,
+ "handler": attribute.handler.id,
+ "enabled": attribute.enabled,
+ "base_unit": attribute.unit,
+ "label": attribute.label,
+ "icon": attribute.icon,
+ "order": attribute.order,
+ "rounding": attribute.rounding,
+ "color": attribute.color,
+ "data": {
+ "value": registered_attribute(attribute).get_display_value(),
+ "unit": registered_attribute(attribute).get_display_unit(),
+ "trend": registered_attribute(attribute).get_trend(),
+ },
+ }
+
+ @blueprint.route("/")
+ @orm.db_session
+ def get_attributes():
+ handler = request.args.get("handler", None)
+ return [
+ attribute_serializer(attribute)
+ for attribute in (Attribute.select(lambda a: a.handler.id == handler) if handler else Attribute.select())
+ ], StatusCode.OK
+
+ @blueprint.route("/")
+ @orm.db_session
+ def get_attribute(attribute_id):
+ try:
+ attribute = Attribute[attribute_id]
+ if attribute:
+ return attribute_serializer(attribute), StatusCode.OK
+ else:
+ return {"message": "Attribute not found."}, StatusCode.NOT_FOUND
+ except orm.ObjectNotFound:
+ return {"message": "Attribute not found."}, StatusCode.NOT_FOUND
+
+ @blueprint.route("/", methods=["PUT"])
+ @orm.db_session
+ def put_attribute(attribute_id):
+ db_attribute = Attribute[attribute_id]
+
+ label = request.json.get("label")
+ db_attribute.label = label if label else None
+
+ unit = request.json.get("base_unit")
+ db_attribute.unit = unit if unit else None
+
+ rounding = request.json.get("rounding")
+ db_attribute.rounding = rounding if rounding is not None else 2
+
+ icon = request.json.get("icon")
+ db_attribute.icon = icon if icon else None
+
+ color = request.json.get("color")
+ db_attribute.color = color if color else None
+
+ attribute = registered_attribute(db_attribute)
+ attribute.refresh_config(db_attribute)
+
+ _context.socketio.emit("mutate", f"core/attributes/{attribute_id}")
+
+ return {"message": "Attribute updated successfully."}, StatusCode.OK
+
+ return blueprint
diff --git a/src/server/modules/blueprints/charts.py b/src/server/modules/blueprints/charts.py
new file mode 100644
index 0000000..3b04ef1
--- /dev/null
+++ b/src/server/modules/blueprints/charts.py
@@ -0,0 +1,57 @@
+from datetime import datetime
+
+from flask import Blueprint
+from pony import orm
+
+from modules.models.data_unit import DataUnit
+from modules.utils import Context, this_name
+
+
+def charts_blueprint(_context: Context):
+ blueprint = Blueprint(this_name(), __name__)
+
+ @blueprint.route("/attribute", defaults={"attribute_ids": [], "date": None})
+ @blueprint.route("/attribute//", defaults={"date": None})
+ @blueprint.route("/attribute//")
+ @orm.db_session
+ def attribute(attribute_ids, date):
+ print("Time of request:\t", datetime.now().time())
+ charts_data = []
+
+ date = datetime.fromisoformat(date).date() if date else datetime.now().date()
+
+ for attribute_id in attribute_ids:
+ data_units = DataUnit.select(
+ lambda data_unit: data_unit.attribute.id == attribute_id and data_unit.date == date
+ )[:]
+
+ print("Time of query:\t\t", datetime.now().time())
+
+ if not data_units or len(data_units) == 0:
+ continue
+
+ first_data_unit = data_units[0]
+ attr = first_data_unit.attribute
+
+ charts_data.append(
+ {
+ "id": attribute_id,
+ "label": attr.label or attr.name,
+ "unit": attr.unit,
+ "color": attr.color,
+ "data": [
+ {
+ "x": datetime.fromisoformat(f"{data_unit.date} {data_unit.time}").timestamp(),
+ "y": data_unit.value,
+ }
+ for data_unit in data_units
+ ],
+ }
+ )
+
+ print("Time of processing:\t", datetime.now().time())
+
+ # Return charts data for Chart.js
+ return charts_data
+
+ return blueprint
diff --git a/src/server/modules/blueprints/datastats.py b/src/server/modules/blueprints/datastats.py
new file mode 100644
index 0000000..a92722b
--- /dev/null
+++ b/src/server/modules/blueprints/datastats.py
@@ -0,0 +1,64 @@
+import datetime
+from datetime import datetime
+
+from flask import Blueprint, request
+from pony import orm
+
+from modules.models.data_stat import DataStat
+from modules.models.unit import Unit
+from modules.utils import Context, this_name, StatusCode
+
+
+def datastats_blueprint(_context: Context):
+ blueprint = Blueprint(this_name(), __name__)
+
+ def data_stat_serializer(data_stat):
+ value = data_stat.value
+ unit = data_stat.attribute.unit
+ stat_value = value
+ stat_unit = unit
+
+ # TODO: Make this more dynamic
+ if len(str(abs(int(value)))) > 3:
+ unit = Unit.select(lambda u: u.base_unit == data_stat.attribute.unit and u.base_ratio == 0.001).first()
+ if unit:
+ stat_value = round(
+ value
+ * Unit.select(lambda u: u.base_unit == data_stat.attribute.unit and u.base_ratio == 0.001)
+ .first()
+ .base_ratio,
+ 2,
+ )
+ stat_unit = (
+ Unit.select(lambda u: u.base_unit == data_stat.attribute.unit and u.base_ratio == 0.001)
+ .first()
+ .name
+ )
+
+ return {
+ "id": data_stat.id,
+ "attribute": data_stat.attribute.id,
+ "type": data_stat.type,
+ "value": stat_value,
+ "unit": stat_unit,
+ "date": data_stat.date,
+ "time": str(data_stat.time),
+ }
+
+ @blueprint.route("/")
+ @orm.db_session
+ def get_datastats():
+ attribute_id = int(request.args.get("attribute"))
+ if attribute_id:
+ return [
+ data_stat_serializer(data_stat)
+ for data_stat in (
+ DataStat.select(lambda d: d.attribute.id == attribute_id and d.date == datetime.now().date())
+ )
+ ], StatusCode.OK
+ return [
+ data_stat_serializer(data_stat)
+ for data_stat in (DataStat.select(lambda d: d.date == datetime.now().date()))
+ ], StatusCode.OK
+
+ return blueprint
diff --git a/src/server/modules/blueprints/handlers.py b/src/server/modules/blueprints/handlers.py
new file mode 100644
index 0000000..b4f6cf2
--- /dev/null
+++ b/src/server/modules/blueprints/handlers.py
@@ -0,0 +1,147 @@
+from flask import Blueprint, request
+from pony import orm
+
+from modules.handlers import get_handler_class, available_handlers
+from modules.models import attribute as attribute_model
+from modules.models import handler as handler_model
+from modules.models.data_stat import DataStat
+from modules.models.data_unit import DataUnit
+from modules.utils import this_name, Context, parse_config, StatusCode, get_current_seconds
+
+
+def handlers_blueprint(_context: Context):
+ blueprint = Blueprint(this_name(), __name__)
+
+ def get_status(handler):
+ """Returns 1 if handler is connected and communication. Returns 2 if handler is connected but not communicating. Returns 0 if handler is not connected."""
+ return 2 if handler.is_connected() and not handler.is_communicating() else 1 if handler.is_connected() else 0
+
+ def handler_serializer(handler):
+ return {
+ "id": handler.get_id(),
+ "type": handler.type,
+ "name": handler.get_name(),
+ "icon": handler.icon,
+ "description": handler.get_description(),
+ "status": get_status(handler),
+ "options": handler.get_options(),
+ "attributes": [
+ {"id": attribute.id, "name": attribute.name}
+ for attribute in handler.get_db_instance().attributes.order_by(attribute_model.Attribute.order)
+ ],
+ "availableAttributes": [
+ {
+ "name": attribute_name,
+ "value": attribute_value,
+ }
+ for attribute_name, attribute_value in {
+ k: v
+ for k, v in _context.manager.last_messages.get(handler.get_id(), {}).items()
+ if k not in _context.manager.registered_attributes.get(handler.get_id(), {}).keys()
+ }.items()
+ ],
+ "last_message": (
+ get_current_seconds() - handler.get_last_message_seconds()
+ if handler.get_last_message_seconds()
+ else None
+ ),
+ "actions": handler.get_actions(),
+ }
+
+ @blueprint.route("/available-handlers")
+ def get_available_handlers():
+ return [
+ {
+ "type": handler.type,
+ "name": handler.name,
+ "icon": handler.icon,
+ "configFields": handler.config_fields,
+ }
+ for handler in available_handlers
+ ], StatusCode.OK
+
+ @blueprint.route("/")
+ @orm.db_session
+ def handlers():
+ return sorted([handler.get_id() for handler in _context.manager.registered_handlers.values()]), StatusCode.OK
+
+ @blueprint.route("/")
+ @orm.db_session
+ def handler_info(handler_id):
+ handler = _context.manager.registered_handlers.get(handler_id, None)
+ if handler:
+ return handler_serializer(handler), StatusCode.OK
+ return {"status": "not found"}, StatusCode.NOT_FOUND
+
+ @blueprint.route("/", methods=["PUT"])
+ @orm.db_session
+ def update_handler(handler_id):
+ handler_db = handler_model.get_by_id(handler_id)
+ if handler_db:
+ attributes = request.json["attributes"]
+
+ # Set attributes order
+ for index, attribute in enumerate(attributes):
+ if attribute["id"] in [a.id for a in handler_db.attributes]:
+ print("Setting order")
+ db_attribute = attribute_model.Attribute[attribute["id"]]
+ db_attribute.order = index
+ db_attribute.flush()
+
+ # TODO: This should be probably moved to post_attribute
+ # If attribute list contains attributes that are not in the handler, add them
+ for attribute in attributes:
+ if attribute["id"] not in [a.id for a in handler_db.attributes]:
+ db_attribute = attribute_model.modify(handler_db, attribute["name"])
+ db_attribute.flush()
+ handler_db.attributes.add(db_attribute)
+ _context.manager.register_attribute(db_attribute)
+
+ # TODO: This should be probably moved to delete_attribute
+ # If attribute list does not contain attributes that are in the handler, remove them
+ for attribute in [{"id": a.id, "name": a.name} for a in handler_db.attributes]:
+ if attribute["name"] not in [a["name"] for a in attributes]:
+ _context.manager.registered_attributes[handler_id].pop(attribute["name"])
+ db_attribute = attribute_model.get_by_id(attribute["id"])
+ DataUnit.select(lambda d: d.attribute == db_attribute).delete()
+ DataStat.select(lambda d: d.attribute == db_attribute).delete()
+ db_attribute.delete()
+ db_attribute.flush()
+
+ _context.socketio.emit("mutate", f"core/handlers/{handler_id}")
+
+ return {"status": "ok"}, StatusCode.OK
+ return {"status": "not found"}, StatusCode.NOT_FOUND
+
+ @blueprint.route("//last")
+ def handler_last_message(handler_id):
+ last_message = _context.manager.last_messages.get(handler_id, None)
+ if last_message:
+ return last_message, StatusCode.OK
+ return {"status": "not found"}, StatusCode.NOT_FOUND
+
+ @blueprint.route("//action/", methods=["PUT"])
+ def handler_action(handler_id, action_name):
+ handler = _context.manager.registered_handlers.get(handler_id, None)
+ if handler:
+ if handler.get_actions and action_name in handler.get_actions():
+ action = handler.get_actions()[action_name]
+ handler.execute_action(action.get("destination"), action.get("params"))
+
+ return {"status": "ok"}, StatusCode.OK
+ return {"status": "not found"}, StatusCode.NOT_FOUND
+
+ @blueprint.route("/add-handler", methods=["POST"])
+ @orm.db_session
+ def add_handler():
+ handler_class = get_handler_class(request.json["type"])
+ options: dict = request.json["options"]
+ options["config"] = parse_config(options.get("config", {}), handler_class)
+ handler = handler_class(options)
+ handler_db = handler_model.add(handler)
+ handler_db.flush()
+ handler.set_db_instance(handler_db)
+ _context.manager.register_handler(handler)
+ return {"status": "ok"}, StatusCode.CREATED
+
+ return blueprint
diff --git a/src/server/modules/blueprints/widgets.py b/src/server/modules/blueprints/widgets.py
new file mode 100644
index 0000000..3ba402e
--- /dev/null
+++ b/src/server/modules/blueprints/widgets.py
@@ -0,0 +1,85 @@
+from flask import Blueprint, request
+from pony import orm
+
+from modules.models.widget_switch import WidgetSwitch
+from modules.models.widget_tile import WidgetTile
+from modules.utils import Context, this_name, StatusCode
+
+
+def widgets_blueprint(_context: Context):
+ blueprint = Blueprint(this_name(), __name__)
+
+ def get_handler(widget):
+ return _context.manager.registered_handlers.get(widget.attribute.handler.id, None)
+
+ def get_attribute(widget):
+ return _context.manager.registered_attributes.get(widget.attribute.handler.id, {}).get(widget.attribute.name)
+
+ def evaluate_attribute(widget):
+ return (
+ (
+ get_attribute(widget).get_current_value() == widget.attribute_compare
+ if widget.attribute_compare
+ else bool(get_attribute(widget).get_current_value())
+ )
+ if get_attribute(widget)
+ else False
+ )
+
+ @blueprint.route("/tiles")
+ @orm.db_session
+ def widget_tiles():
+ return [
+ {
+ "id": tile.id,
+ "name": tile.attribute.label or tile.attribute.name, # TODO: Split label and name
+ "description": get_handler(tile).get_name(),
+ "handler": tile.attribute.handler.id,
+ "status": 1 if get_handler(tile).is_connected() else 0,
+ "icon": tile.attribute.icon,
+ "attribute": tile.attribute.name,
+ "unit": tile.attribute.unit,
+ "value": get_attribute(tile).get_current_value() if get_attribute(tile) else None,
+ }
+ for tile in WidgetTile.select()
+ ], StatusCode.OK
+
+ @blueprint.route("/switches")
+ @orm.db_session
+ def widget_switches():
+ return [
+ {
+ "id": switch.id,
+ "name": switch.name,
+ "description": get_handler(switch).get_name(),
+ "handler": switch.attribute.handler.id,
+ "status": 1 if get_handler(switch).is_connected() else 0,
+ "icon": switch.icon,
+ "attribute": switch.attribute.name,
+ "active": evaluate_attribute(switch),
+ }
+ for switch in WidgetSwitch.select()
+ ], StatusCode.OK
+
+ @blueprint.route("/switches/toggle/", methods=["POST"])
+ @orm.db_session
+ def widget_switches_toggle(switch_id):
+ value = request.json.get("value", None)
+ for switch in WidgetSwitch.select(lambda s: s.id == switch_id):
+ handler = get_handler(switch)
+ if value:
+ handler.send_message({"label": switch.action_on.message})
+ else:
+ handler.send_message({"label": switch.action_off.message})
+ return {
+ "id": switch.id,
+ "name": switch.name,
+ "description": get_handler(switch).get_name(),
+ "handler": switch.attribute.handler.id,
+ "icon": switch.icon,
+ "attribute": switch.attribute.name,
+ "active": evaluate_attribute(switch),
+ }, StatusCode.OK
+ return {"status": "not found"}, StatusCode.NOT_FOUND
+
+ return blueprint
diff --git a/src/server/modules/database/__init__.py b/src/server/modules/database/__init__.py
new file mode 100644
index 0000000..ecb0375
--- /dev/null
+++ b/src/server/modules/database/__init__.py
@@ -0,0 +1,20 @@
+from pony import orm
+
+db = orm.Database()
+
+
+def init_database(db_config: dict):
+ """Initializes database"""
+ provider = db_config.get("provider")
+ if provider == "postgres":
+ db.bind(
+ provider=provider,
+ user=db_config.get("user", "contwatch"),
+ password=db_config.get("password", "contwatch"),
+ host=db_config.get("host", "localhost"),
+ database=db_config.get("database", "contwatch"),
+ )
+ elif provider == "sqlite" or provider is None:
+ db.bind(provider="sqlite", filename=db_config.get("filename", "database.sqlite"), create_db=True)
+
+ db.generate_mapping(create_tables=True)
diff --git a/src/server/modules/handler_manager.py b/src/server/modules/handler_manager.py
new file mode 100644
index 0000000..0ef069b
--- /dev/null
+++ b/src/server/modules/handler_manager.py
@@ -0,0 +1,197 @@
+from datetime import datetime, time
+from threading import Thread
+from time import sleep
+
+from pony import orm
+
+from modules.actions.nodes import NODES_MAP
+from modules.actions.nodes.abstract_node import AbstractNode
+from modules.attribute_manager import AttributeManager
+from modules.handlers import get_handler_class
+from modules.handlers.abstract_handler import AbstractHandler
+from modules.logging import Logger
+from modules.models import data_unit as data_unit_model
+from modules.models import handler as handler_model
+from modules.models.settings import get_settings
+from modules.utils import linearize, Context
+
+
+class HandlerManager:
+ """Handles registration and communication of handlers"""
+
+ def _handler_watcher(self):
+ sleep(2)
+ while self.active:
+ for h_id, handler in self.registered_handlers.items():
+ if handler.ready_to_read():
+ message = handler.read_message()
+ self.process_message(h_id, message)
+ sleep(0.01)
+
+ @orm.db_session
+ def __init__(self, socketio):
+ print("Initializing HandlerManager...")
+ self.socketio = socketio
+ self.active = True
+ self.log = Logger("HandlerManager")
+
+ settings = get_settings(1)
+
+ # Dictionary for handler instances
+ self.registered_handlers: dict = {}
+ # Dictionary for last received messages from handlers
+ self.last_messages: dict = {}
+ # Dictionary for attribute instances
+ self.registered_attributes = {}
+
+ self.initialize_handlers()
+
+ # NodeMap for actions from database
+ self.actions_node_map = settings.actions_node_map if settings else {}
+ # Actual node instances
+ self.actions_node_instances_map = {}
+
+ self.rebuild_nodes()
+
+ self.thread = Thread(target=self._handler_watcher)
+ self.thread.start()
+ print("Initialized HandlerManager.")
+
+ @orm.db_session
+ def initialize_handlers(self):
+ """Load handlers from database and initialize them"""
+ db_handlers = handler_model.get_all()
+ self.log.info(f"Loaded {len(db_handlers)} handlers from database")
+ for db_handler in db_handlers:
+ handler = get_handler_class(db_handler.type)(db_handler.options)
+ handler.set_db_instance(db_handler)
+ print("db_instance set for handler:", handler.get_id(), handler.type)
+ last_data_unit_select = data_unit_model.get_by_handler_id(db_handler.id)
+ last_data_unit = last_data_unit_select[-1] if last_data_unit_select else None
+ if last_data_unit:
+ handler.set_last_message_seconds(
+ datetime.combine(last_data_unit.date, time.fromisoformat(str(last_data_unit.time))).timestamp()
+ )
+ self.register_handler(handler)
+ print("register_handler called for handler:", handler.get_id(), handler.type)
+ for db_attribute in db_handler.attributes:
+ self.register_attribute(db_attribute)
+ print("Finished initializing handler:", handler.get_id(), handler.type)
+
+ def register_handler(self, handler):
+ """Add handler instance to the dictionary"""
+ handler_id = handler.get_id()
+ self.registered_handlers[handler_id] = handler
+
+ def register_attribute(self, db_attribute):
+ """Add attribute instance to the dictionary"""
+ print("Registering attribute:", db_attribute.name)
+ handler_id = db_attribute.handler.id
+ if handler_id not in self.registered_attributes:
+ self.registered_attributes[handler_id] = {}
+ self.registered_attributes[handler_id][db_attribute.name] = AttributeManager(db_attribute, self.socketio)
+
+ def process_message(self, handler_id: AbstractHandler, message):
+ linearized_json = linearize(message)
+ self.last_messages[handler_id] = linearized_json
+ # print(linearized_json)
+
+ stored_attributes = self.registered_attributes.get(handler_id, {}).copy()
+
+ emit_handler_mutation = False
+
+ for attribute in stored_attributes:
+ emit_attribute_mutation = False
+
+ if attribute in linearized_json:
+ attribute_instance: AttributeManager = stored_attributes.get(attribute)
+
+ if attribute_instance.add_data_unit(linearized_json.get(attribute)):
+ # If value has changed, execute listeners
+ self.execute_attribute_listeners(attribute_instance.get_id())
+ emit_attribute_mutation = True
+ emit_handler_mutation = True
+
+ if emit_attribute_mutation:
+ self.socketio.emit("mutate", f"core/attributes/{attribute_instance.get_id()}")
+
+ self.execute_handler_listeners(handler_id)
+
+ if emit_handler_mutation:
+ self.socketio.emit("mutate", f"core/handlers/{handler_id}")
+
+ def execute_handler_listeners(self, handler_id):
+ """Find listeners for this handler and execute them."""
+ for node_id, node in self.actions_node_instances_map.items():
+ node: AbstractNode
+ if node.node_settings.get("type") == "HandlerListener":
+ if node.node_settings.get("inputData", {}).get("Handler", {}).get("select") == handler_id:
+ node.execute()
+
+ def execute_attribute_listeners(self, attribute_id):
+ """Find listeners for this attribute and execute them."""
+ for node_id, node in self.actions_node_instances_map.items():
+ node: AbstractNode
+ if node.node_settings.get("type") == "AttributeReaderListener":
+ if node.node_settings.get("inputData", {}).get("Attribute", {}).get("select") == attribute_id:
+ node.execute()
+
+ def set_actions_node_map(self, actions_node_map):
+ self.actions_node_map = actions_node_map
+ self.rebuild_nodes()
+
+ def rebuild_nodes(self):
+ """Parse actions_node_map and create node instances"""
+ self.actions_node_instances_map.clear()
+ for node_id, node in self.actions_node_map.items():
+ node: dict
+
+ new_node_instance = self.actions_node_instances_map.get(node_id)
+ new_node_instance: AbstractNode
+ if not new_node_instance:
+ new_node_instance = NODES_MAP.get(node.get("type"))(Context(self), node)
+ self.actions_node_instances_map[node_id] = new_node_instance
+ else:
+ new_node_instance.load_node_settings(node)
+
+ # Add input connections to the node
+ for port_name, connection_list in node.get("connections", {}).get("inputs", {}).items():
+ for connection in connection_list:
+ connection: dict
+ connection_id = connection.get("nodeId")
+ connection_name = self.actions_node_map.get(connection_id).get("type")
+
+ input_node_instance = self.actions_node_instances_map.get(connection_id)
+ # If node instance is not created yet, create it
+ if not input_node_instance:
+ input_node_instance = NODES_MAP.get(connection_name)(Context(self))
+ self.actions_node_instances_map[connection_id] = input_node_instance
+
+ new_node_instance.add_input_connection(
+ port_name,
+ input_node_instance,
+ )
+
+ # Add output connections to the node
+ for port_name, connection_list in node.get("connections", {}).get("outputs", {}).items():
+ for connection in connection_list:
+ connection: dict
+ connection_id = connection.get("nodeId")
+ connection_name = self.actions_node_map.get(connection_id).get("type")
+
+ output_node_instance = self.actions_node_instances_map.get(connection_id)
+ # If node instance is not created yet, create it
+ if not output_node_instance:
+ output_node_instance = NODES_MAP.get(connection_name)(Context(self))
+ self.actions_node_instances_map[connection_id] = output_node_instance
+
+ new_node_instance.add_output_connection(
+ port_name,
+ output_node_instance,
+ )
+
+ def exit(self):
+ for handler in self.registered_handlers:
+ self.registered_handlers[handler].exit()
+ self.active = False
+ self.log.debug(f"Terminating")
diff --git a/src/server/modules/handlers/__init__.py b/src/server/modules/handlers/__init__.py
new file mode 100644
index 0000000..bb776a1
--- /dev/null
+++ b/src/server/modules/handlers/__init__.py
@@ -0,0 +1,21 @@
+from .abstract_handler import AbstractHandler
+from .http_handler import HttpHandler
+from .jiabaida_bms_serial_handler import JiabaidaBmsSerialHandler
+from .must_pv_ph_inverter_modbus_handler import MustPVPHInverterModbusHandler
+from .serial_handler import SerialHandler
+from .shelly_plug import ShellyPlugHandler
+
+available_handlers = [
+ HttpHandler,
+ SerialHandler,
+ JiabaidaBmsSerialHandler,
+ MustPVPHInverterModbusHandler,
+ ShellyPlugHandler,
+]
+
+
+def get_handler_class(handler_type):
+ for handler in available_handlers:
+ if handler.type == handler_type:
+ return handler
+ return None
diff --git a/src/server/modules/handlers/abstract_handler.py b/src/server/modules/handlers/abstract_handler.py
new file mode 100644
index 0000000..2b14eb4
--- /dev/null
+++ b/src/server/modules/handlers/abstract_handler.py
@@ -0,0 +1,176 @@
+from time import sleep
+
+from pony.utils import throw
+
+from modules.utils import get_current_seconds
+
+
+class AbstractHandler:
+ """Abstract class which specifies methods each handler class should implement."""
+
+ # Each handler class should have the following variables configured:
+
+ type = ""
+ """Type of the handler"""
+
+ name = "Unknown"
+ """Name of the handler"""
+
+ icon = "default"
+ """Iconname of handler displayed in GUI"""
+
+ config_fields = {}
+ """Configuration form definition for initialization from GUI"""
+
+ handler_actions = {}
+ """Dict of actions available for the handler"""
+
+ # Each handler class can and should implement the following methods:
+
+ def get_name(self):
+ """Returns the standardized name of the handler for GUI."""
+ return self.get_option("label", self.name)
+
+ def get_description(self):
+ """Returns description of the handler displayed in GUI."""
+ return f"{self.type} handler"
+
+ def execute_action(self, destination, params):
+ """Execute handler action."""
+ throw(NotImplementedError)
+
+ def is_connected(self):
+ """Returns True if the target is connected and ready to use."""
+ return False
+
+ def is_communicating(self):
+ """Returns True if the target is responding to messages."""
+ return False
+
+ def exit(self):
+ """Signal to disconnect from target and exit all threads."""
+ self.active = False
+
+ # Default attributes and methods of each handler instance:
+
+ options = {}
+ """Dictionary containing handler settings options.
+ It is serialized as a JSON to the database."""
+
+ # changed = []
+ # """Contains appropriate string if there is a need to refresh GUI.
+ # Use add_changed() to append here."""
+
+ def __init__(self, options):
+ print(f"Initializing {self.name} handler")
+ self.active = False
+ self.options = options or {}
+ self.message_queue = []
+ self._db_instance = None
+ self._first_tick = True
+ self._current_seconds = get_current_seconds()
+ self._last_seconds = 0
+ self._last_message_seconds = None
+
+ def get_options(self):
+ return self.options
+
+ def get_option(self, attribute, default=None):
+ """Returns single setting option attribute value"""
+ return self.get_options().get(attribute, default)
+
+ def set_option(self, attribute, value):
+ """Sets single setting option attribute value"""
+ self.get_options()[attribute] = value
+
+ def get_config(self):
+ """Returns configuration form values"""
+ return self.get_option("config", {})
+
+ def set_config(self, new_config):
+ """Update handler configuration accordingly."""
+ if "config" not in self.get_options():
+ self.set_option("config", {})
+ for attribute in new_config:
+ self.get_option("config")[attribute] = new_config[attribute]
+ # self.add_changed("handlers")
+
+ def get_config_option(self, attribute):
+ """Returns single form configuration option value"""
+ return self.get_config().get(attribute, None)
+
+ def get_last_message_seconds(self):
+ """Returns the time in seconds when the last message was received or 0 if no message was received."""
+ return self._last_message_seconds or 0
+
+ def set_last_message_seconds(self, seconds):
+ """Sets the time in seconds when the last message was received."""
+ self._last_message_seconds = seconds
+
+ def get_actions(self):
+ """Returns list of actions available for the handler."""
+ return self.handler_actions
+
+ # def add_changed(self, value):
+ # """
+ # Add appropriate string if there is a need to refresh GUI.
+ # ["overview", "inspector", "actions", "data", "handlers", "details"]
+ # """
+ # if value not in self.changed:
+ # self.changed.append(value)
+
+ # def add_storage_attribute(self, attribute):
+ # if "storage-attributes" not in self.settings:
+ # self.settings["storage-attributes"] = []
+ # if attribute not in self.settings["storage-attributes"]:
+ # self.settings["storage-attributes"].append(attribute)
+
+ # def get_storage_attributes(self):
+ # if "storage-attributes" in self.settings:
+ # return self.settings["storage-attributes"]
+ # return []
+ #
+ # def clear_storage_attributes(self):
+ # self.settings["storage-attributes"] = []
+
+ # def set_label(self, label):
+ # self.set_option("label", label)
+
+ def add_message(self, message):
+ """Appends message to the message queue and updates the last message seconds."""
+ self.message_queue.append(message)
+ self._last_message_seconds = get_current_seconds()
+
+ def ready_to_read(self):
+ """Returns True if there is a message ready to read."""
+ return len(self.message_queue) > 0
+
+ def read_message(self):
+ """Returns the oldest message from the message queue."""
+ if self.ready_to_read():
+ return self.message_queue.pop(0)
+ return None
+
+ def first_tick(self) -> bool:
+ """Returns True only when called for the first time."""
+ result = self._first_tick
+ self._first_tick = False
+ return result
+
+ def wait_for_interval(self, interval):
+ """Wait until current seconds % interval equals 0."""
+ self._current_seconds = get_current_seconds()
+ while self.active and (self._current_seconds % interval != 0 or self._current_seconds == self._last_seconds):
+ self._current_seconds = get_current_seconds()
+ sleep(0.1)
+
+ self._last_seconds = self._current_seconds
+
+ def get_id(self) -> int:
+ return self._db_instance.id
+
+ def set_db_instance(self, db_instance):
+ self._db_instance = db_instance
+
+ def get_db_instance(self):
+ return self._db_instance
diff --git a/src/server/modules/handlers/http_handler.py b/src/server/modules/handlers/http_handler.py
new file mode 100644
index 0000000..69c2525
--- /dev/null
+++ b/src/server/modules/handlers/http_handler.py
@@ -0,0 +1,167 @@
+from ssl import SSLError
+from threading import Thread
+from time import sleep
+
+from requests import get
+from requests.exceptions import ConnectionError, ReadTimeout, MissingSchema
+from simplejson import JSONDecodeError
+
+from modules.logging import Logger
+from .abstract_handler import AbstractHandler
+from ..utils import get_current_seconds
+
+
+class HttpHandler(AbstractHandler):
+ """Class for handling HTTP targets."""
+
+ def _fetcher(self):
+ self.log.debug("Starting data fetcher")
+ self.success = False
+ # self.add_changed("handlers")
+ while self.active:
+ response = None
+ if self.success:
+ self.wait_for_interval(self.get_config_option("interval"))
+ try:
+ response = get(self.get_fetch_url(), timeout=self.get_config_option("timeout"))
+ if response.status_code == 200:
+ self.last_response = response
+ self.add_message(response.json())
+ if not self.success:
+ pass
+ # self.add_changed("handlers")
+ self.success = True
+ elif response.status_code == 404:
+ # message = response.text
+ # if self.get_config_option("json"):
+ message = response.json()
+ print(response.url, message)
+ self.success = True
+ else:
+ self.success = False
+ # self.add_changed("handlers")
+ Thread(target=self._reconnect_watcher).start()
+ break
+ except ConnectionError as error:
+ self._handle_error(error, "Failed to establish a connection")
+ break
+ except ReadTimeout as error:
+ self._handle_error(error, "Connection timeout")
+ break
+ except MissingSchema as error:
+ self._handle_error(error, "Invalid URL address")
+ break
+ except JSONDecodeError as error:
+ self._handle_error(error, "Json decode error")
+ break
+ except SSLError as error:
+ self._handle_error(error, "SSL error")
+ break
+ sleep(0.1)
+ self.log.debug("Stopping fetcher")
+
+ def _reconnect_watcher(self):
+ self.log.debug("Starting reconnect watcher")
+ while self.active:
+ try:
+ response = get(self.get_fetch_url(), timeout=self.get_config_option("timeout"))
+ if response.status_code == 200:
+ # if self.get_config_option("json"):
+ response.json()
+ Thread(target=self._fetcher).start()
+ # self.add_changed("handlers")
+ break
+ except ConnectionError:
+ pass
+ except ReadTimeout:
+ pass
+ except MissingSchema:
+ pass
+ except JSONDecodeError:
+ pass
+ except SSLError:
+ pass
+ sleep(1)
+ self.log.debug("Stopping reconnect watcher")
+
+ def _handle_error(self, error, message):
+ self.success = False
+ self.log.error(message, {"error": error})
+ # self.add_changed("handlers")
+ Thread(target=self._reconnect_watcher).start()
+
+ type = "http"
+ icon = type
+ name = "HTTP API"
+ config_fields = {
+ "host": ["string", "Host address"],
+ "fetch_route": ["string", "Fetch route"],
+ "interval": ["int", "Fetching interval in seconds", 10],
+ "timeout": ["float", "Timeout in seconds", 3],
+ # "json": ["bool", "Parse as a JSON", False],
+ }
+
+ def __init__(self, settings):
+ super().__init__(settings)
+ self.log = Logger(f"{self.name} {self.get_fetch_url()}")
+ self.success = False
+ self.active = True
+ self.last_response = None
+ # self.add_changed("handlers")
+ Thread(target=self._fetcher).start()
+ print(f"Initialized {self.name}")
+
+ def get_host_url(self):
+ host_url = self.get_config_option("host")
+ if "http://" not in host_url and "https://" not in host_url:
+ return "http://" + host_url
+ return host_url
+
+ def get_fetch_url(self):
+ return self.get_host_url() + self.get_config_option("fetch_route")
+
+ def get_description(self):
+ return self.get_fetch_url()
+
+ def execute_action(self, destination, params):
+ try:
+ host_url = self.get_host_url()
+ # TODO: Implement proper message class?
+ target = f"{host_url}{'/' if destination[0] != '/' else ''}{destination}"
+ response = get(target, params=params, timeout=self.get_config_option("timeout"))
+ # TODO: Maybe use response.ok instead?
+ if response.status_code:
+ if self.last_response:
+ response_keys = list(response.json().keys())
+ response_keys.sort()
+ last_message_keys = list(self.last_response.json().keys())
+ last_message_keys.sort()
+ if response_keys == last_message_keys:
+ self.add_message(response.json())
+ else:
+ response = get(self.get_fetch_url(), timeout=self.get_config_option("timeout"))
+ if response.status_code == 200:
+ self.last_response = response
+ self.add_message(response.json())
+ else:
+ response = get(self.get_fetch_url(), timeout=self.get_config_option("timeout"))
+ if response.status_code == 200:
+ self.last_response = response
+ self.add_message(response.json())
+ return True
+ except JSONDecodeError as error:
+ print(error)
+ except ConnectionError as error:
+ print(error)
+ except ReadTimeout as error:
+ print(error)
+ except MissingSchema as error:
+ print(error)
+
+ def is_connected(self):
+ return self.success
+
+ def is_communicating(self):
+ return self.get_last_message_seconds() > (
+ get_current_seconds() - self.get_config_option("interval") - self.get_config_option("timeout")
+ )
diff --git a/modules/handlers/bms_serial_handler.py b/src/server/modules/handlers/jiabaida_bms_serial_handler.py
similarity index 68%
rename from modules/handlers/bms_serial_handler.py
rename to src/server/modules/handlers/jiabaida_bms_serial_handler.py
index 0f5f6d5..c9be2b7 100644
--- a/modules/handlers/bms_serial_handler.py
+++ b/src/server/modules/handlers/jiabaida_bms_serial_handler.py
@@ -1,4 +1,5 @@
from serial import SerialException
+
from .serial_handler import SerialHandler
@@ -6,12 +7,12 @@ def _byte(array, index):
return array[index] * 256 + array[index + 1]
-class BmsSerialHandler(SerialHandler):
- """Class for handling Jiabaida Battery Management System V4 connected to serial port."""
+class JiabaidaBmsSerialHandler(SerialHandler):
+ """Class for handling Jiabaida Battery Management System connected to serial port."""
- type = "bms_serial"
- icon = type
- name = "Jiabaida BMS V4"
+ type = "jiabaida_bms_serial"
+ icon = "battery"
+ name = "Jiabaida BMS"
config_fields = {
"port": ["string", "Device port (e.g., /dev/ttyUSB0)"],
"interval": ["int", "Fetching interval in seconds", 10],
@@ -19,6 +20,24 @@ class BmsSerialHandler(SerialHandler):
"auto-reconnect": ["bool", "Auto reconnect", True],
"trim-echo": ["bool", "Trim echoed messages", False],
}
+ handler_actions = {
+ "00": {
+ "description": "00",
+ "params": {"mos_state": "00"},
+ },
+ "01": {
+ "description": "01",
+ "params": {"mos_state": "01"},
+ },
+ "10": {
+ "description": "10",
+ "params": {"mos_state": "10"},
+ },
+ "11": {
+ "description": "11",
+ "params": {"mos_state": "11"},
+ },
+ }
def _read_block(self, query):
self.connection.flushInput()
@@ -29,7 +48,7 @@ def _read_block(self, query):
# TODO: Auto detect echoed messages and trim them automatically.
incoming_length = 11
- if not self.config("trim-echo"):
+ if not self.get_config_option("trim-echo"):
incoming_length = 4
for i in range(0, incoming_length):
byte = int.from_bytes(self.connection.read(), "big")
@@ -49,19 +68,15 @@ def _read_block(self, query):
def _read_message(self):
if not self.first_tick():
- self.wait_for_interval(self.config("interval"))
+ self.wait_for_interval(self.get_config_option("interval"))
- d1 = self._read_block(b"\xDD\xA5\x03\x00\xFF\xFD\x77")
- d2 = self._read_block(b"\xDD\xA5\x04\x00\xFF\xFC\x77")
+ d1 = self._read_block(b"\xdd\xa5\x03\x00\xff\xfd\x77")
+ d2 = self._read_block(b"\xdd\xa5\x04\x00\xff\xfc\x77")
if not d1 or not d2:
raise SerialException("Missing some block of data")
- current = (
- _byte(d1, 2) / 100
- if _byte(d1, 2) < 2**15
- else (_byte(d1, 2) - 2**16) / 100
- ) or 0
+ current = (_byte(d1, 2) / 100 if _byte(d1, 2) < 2**15 else (_byte(d1, 2) - 2**16) / 100) or 0
json = {
"voltage": _byte(d1, 0) / 100,
@@ -71,15 +86,16 @@ def _read_message(self):
"cycles": _byte(d1, 8),
"percentages": d1[19],
"mos-state": d1[20],
- "temperatures": {
- # TODO: Number of temps is provided in data too, do this in loop.
- "1": (_byte(d1, 23) - 2731) / 10,
- "2": (_byte(d1, 25) - 2731) / 10,
- },
+ "temperatures": {},
"cells": {},
"protection-bits": bin(_byte(d1, 16))[2:].zfill(16),
}
+ temperatures_count = d1[22]
+
+ for i in range(0, temperatures_count):
+ json["temperatures"][f"{i+1}"] = (_byte(d1, 23 + i * 2) - 2731) / 10
+
cell_count = d1[21]
balancing = bin(_byte(d1, 14))[2:].zfill(16) + bin(_byte(d1, 12))[2:].zfill(16)
@@ -91,13 +107,12 @@ def _read_message(self):
return json
- def send_message(self, message):
+ def execute_action(self, destination, params):
if self.is_connected():
try:
mos_template = "DD 5A E1 02 00 ## ?? ?? 77"
- label = message.get_label()
- if "mos-state" in label:
- bits = label.split("-")[2]
+ if "mos_state" in params:
+ bits = params["mos_state"]
text = mos_template
if bits not in ["00", "01", "10", "11"]:
return False
diff --git a/modules/handlers/must_pv_ph_inverter_modbus_handler.py b/src/server/modules/handlers/must_pv_ph_inverter_modbus_handler.py
similarity index 65%
rename from modules/handlers/must_pv_ph_inverter_modbus_handler.py
rename to src/server/modules/handlers/must_pv_ph_inverter_modbus_handler.py
index 8b1d80e..007448d 100644
--- a/modules/handlers/must_pv_ph_inverter_modbus_handler.py
+++ b/src/server/modules/handlers/must_pv_ph_inverter_modbus_handler.py
@@ -1,11 +1,13 @@
-from minimalmodbus import Instrument, NoResponseError, InvalidResponseError
from os import path
-from serial import SerialException
from threading import Thread
from time import sleep
-from modules.logging.logger import logger
+from minimalmodbus import Instrument, NoResponseError, InvalidResponseError
+from serial import SerialException
+
+from modules.logging.logger import Logger
from .abstract_handler import AbstractHandler
+from ..utils import get_current_seconds
class MustPVPHInverterModbusHandler(AbstractHandler):
@@ -39,15 +41,15 @@ class MustPVPHInverterModbusHandler(AbstractHandler):
def _read_message(self):
if not self.first_tick():
- self.wait_for_interval(self.config("interval"))
+ self.wait_for_interval(self.get_config_option("interval"))
result = {"charger": {}, "inverter": {}}
+ self.connection.serial.reset_input_buffer()
+
for section_type in self.registers.keys():
for key, data in self.registers[section_type].items():
- result[section_type][key] = self.connection.read_register(
- data[0], data[1]
- )
+ result[section_type][key] = self.connection.read_register(data[0], data[1])
sleep(0.05)
return result
@@ -62,6 +64,10 @@ def _message_watcher(self):
self.add_message(message)
except SerialException as error:
self._handle_error(error, "Failed to read from device")
+ if path.exists(self.get_config_option("port")):
+ self._handle_error(error, "Failed to establish connection - Permission denied")
+ else:
+ self._handle_error(error, "Failed to establish connection - Device does not exist")
break
except UnicodeDecodeError as error:
self.log.warning(error)
@@ -75,8 +81,9 @@ def _message_watcher(self):
else:
self.log.info("Lost connection with device")
self.connection.serial.close()
- self.add_changed("handlers")
- if self.config("auto-reconnect"):
+ self.connection = None
+ # self.add_changed("handlers")
+ if self.get_config_option("auto-reconnect"):
Thread(target=self._reconnect_watcher).start()
else:
self.suspended = True
@@ -87,34 +94,32 @@ def _handle_error(self, error, message):
# print(error)
self.log.warning(message)
self.log.error(error)
- self.success = False
- self.add_changed("handlers")
+ # self.add_changed("handlers")
Thread(target=self._reconnect_watcher).start()
def _reconnect_watcher(self):
self.log.debug("Starting reconnect watcher")
while self.active:
- if path.exists(self.connection.serial.port):
- if self._reconnect():
- Thread(target=self._message_watcher).start()
- break
+ if self._reconnect():
+ Thread(target=self._message_watcher).start()
+ break
sleep(1)
self.log.debug("Stopping reconnect watcher")
def _reconnect(self):
try:
- self.connection.serial.open()
+ if self.connection:
+ self.connection.serial.close()
+ self.connection = None
+ self.connection = Instrument(self.get_config_option("port"), self.get_config_option("slave-address"))
+ self.connection.serial.timeout = self.get_config_option("timeout")
+ if not self.connection.serial.is_open:
+ self.connection.serial.open()
+ self._read_message()
self.log.info("Established connection with device")
- self.add_changed("handlers")
+ # self.add_changed("handlers")
return True
except SerialException:
- if path.exists(self.connection.serial.port):
- self.log.warning("Failed to establish connection - Permission denied")
- else:
- self.log.warning(
- "Failed to establish connection - Device does not exist"
- )
- self.connection.serial.close()
return False
except NoResponseError:
return False
@@ -123,37 +128,36 @@ def _reconnect(self):
def __init__(self, settings):
super().__init__(settings)
- self.log = logger(
- f"SerialDevice {self.config('port')}:{self.config('slave-address')}"
- )
-
- self.connection = Instrument(self.config("port"), self.config("slave-address"))
- self.connection.serial.timeout = self.config("timeout")
+ self.log = Logger(f"{self.name} {self.get_config_option('port')}:{self.get_config_option('slave-address')}")
+ self.connection = None
self.active = True
self.suspended = False
- self.add_changed("handlers")
+ # self.add_changed("handlers")
Thread(target=self._reconnect_watcher).start()
+ print(f"Initialized {self.name}")
- def update_config(self, new_config):
- super().update_config(new_config)
+ def set_config(self, new_config):
+ super().set_config(new_config)
# TODO: Semaphore may be required
self.connection.serial.close()
- self.connection.serial.port = self.config("port")
- self.connection.serial.timeout = self.config("timeout")
- self.connection.address = self.config("slave-address")
- self.connection.serial.close()
+ self.connection = None
if self.suspended:
Thread(target=self._reconnect_watcher).start()
- self.add_changed("handlers")
+ # self.add_changed("handlers")
def get_description(self):
- return self.connection.serial.port
+ return self.get_config_option("port")
def is_connected(self):
- return self.connection.serial.is_open
+ return self.connection and self.connection.serial.is_open
+
+ def is_communicating(self):
+ return self.get_last_message_seconds() > (
+ get_current_seconds() - self.get_config_option("interval") - self.get_config_option("timeout")
+ )
diff --git a/modules/handlers/serial_handler.py b/src/server/modules/handlers/serial_handler.py
similarity index 74%
rename from modules/handlers/serial_handler.py
rename to src/server/modules/handlers/serial_handler.py
index 323ebe8..59f150b 100644
--- a/modules/handlers/serial_handler.py
+++ b/src/server/modules/handlers/serial_handler.py
@@ -1,12 +1,14 @@
from json import loads
from json.decoder import JSONDecodeError
from os import path
-from serial import Serial, SerialException
from threading import Thread
from time import sleep
-from modules.logging.logger import logger
+from serial import Serial, SerialException
+
+from modules.logging import Logger
from .abstract_handler import AbstractHandler
+from ..utils import get_current_seconds
class SerialHandler(AbstractHandler):
@@ -30,6 +32,7 @@ def _message_watcher(self):
while self.active:
if path.exists(self.connection.port):
try:
+ self.connection.reset_input_buffer()
message = self._read_message()
if message:
self.add_message(message)
@@ -43,8 +46,8 @@ def _message_watcher(self):
else:
self.log.info("Lost connection with device")
self.connection.close()
- self.add_changed("handlers")
- if self.config("auto-reconnect"):
+ # self.add_changed("handlers")
+ if self.get_config_option("auto-reconnect"):
Thread(target=self._reconnect_watcher).start()
else:
self.suspended = True
@@ -65,15 +68,13 @@ def _reconnect(self):
try:
self.connection.open()
self.log.info("Established connection with device")
- self.add_changed("handlers")
+ # self.add_changed("handlers")
return True
except SerialException:
if path.exists(self.connection.port):
self.log.warning("Failed to establish connection - Permission denied")
else:
- self.log.warning(
- "Failed to establish connection - Device does not exist"
- )
+ self.log.warning("Failed to establish connection - Device does not exist")
self.connection.close()
return False
@@ -89,34 +90,35 @@ def _reconnect(self):
def __init__(self, settings):
super().__init__(settings)
- self.log = logger(f"SerialDevice {self.config('port')}")
+ self.log = Logger(f"{self.name} {self.get_config_option('port')}")
self.connection = Serial()
- self.connection.port = self.config("port")
- self.connection.baudrate = self.config("baudrate") or 9600
- self.connection.timeout = self.config("timeout")
+ self.connection.port = self.get_config_option("port")
+ self.connection.baudrate = self.get_config_option("baudrate") or 9600
+ self.connection.timeout = self.get_config_option("timeout")
self.active = True
self.suspended = False
- self.add_changed("handlers")
+ # self.add_changed("handlers")
Thread(target=self._reconnect_watcher).start()
+ print(f"Initialized {self.name}")
- def update_config(self, new_config):
- super().update_config(new_config)
+ def set_config(self, new_config):
+ super().set_config(new_config)
# TODO: Semaphore may be required
self.connection.close()
- self.connection.port = self.config("port")
- self.connection.baudrate = self.config("baudrate") or 9600
- self.connection.timeout = self.config("timeout")
+ self.connection.port = self.get_config_option("port")
+ self.connection.baudrate = self.get_config_option("baudrate") or 9600
+ self.connection.timeout = self.get_config_option("timeout")
self.connection.close()
if self.suspended:
Thread(target=self._reconnect_watcher).start()
- self.add_changed("handlers")
+ # self.add_changed("handlers")
def get_description(self):
return self.connection.port
@@ -130,8 +132,14 @@ def send_message(self, message):
pass
def is_connected(self):
+ # TODO: Check if the messages are being sent
return self.connection.is_open
+ def is_communicating(self):
+ return self.get_last_message_seconds() > (
+ get_current_seconds() - self.get_config_option("interval") - self.get_config_option("timeout")
+ )
+
def exit(self):
self.active = False
if self.connection:
diff --git a/src/server/modules/handlers/shelly_plug.py b/src/server/modules/handlers/shelly_plug.py
new file mode 100644
index 0000000..7a9c364
--- /dev/null
+++ b/src/server/modules/handlers/shelly_plug.py
@@ -0,0 +1,21 @@
+from modules.handlers import HttpHandler
+
+
+class ShellyPlugHandler(HttpHandler):
+ """Handler for Shelly Plug"""
+
+ type = "shelly_plug"
+ icon = "powerplug"
+ name = "Shelly Plug"
+ handler_actions = {
+ "turn_on": {
+ "description": "Turn on",
+ "destination": "/relay/0",
+ "params": {"turn": "on"},
+ },
+ "turn_off": {
+ "description": "Turn off",
+ "destination": "/relay/0",
+ "params": {"turn": "off"},
+ },
+ }
diff --git a/src/server/modules/logging/__init__.py b/src/server/modules/logging/__init__.py
new file mode 100644
index 0000000..92a2f16
--- /dev/null
+++ b/src/server/modules/logging/__init__.py
@@ -0,0 +1 @@
+from .logger import Logger
diff --git a/src/server/modules/logging/logger.py b/src/server/modules/logging/logger.py
new file mode 100644
index 0000000..e2cff96
--- /dev/null
+++ b/src/server/modules/logging/logger.py
@@ -0,0 +1,57 @@
+import logging
+
+from modules.utils import color_print, Color
+
+
+class LoggingLevel:
+ DEBUG = "debug"
+ INFO = "info"
+ WARNING = "warning"
+ ERROR = "error"
+ CRITICAL = "critical"
+
+
+class Logger:
+ """Class for application-wide logging"""
+
+ level_colors = {
+ LoggingLevel.DEBUG: Color.PINK,
+ LoggingLevel.INFO: Color.GREEN,
+ LoggingLevel.WARNING: Color.ORANGE,
+ LoggingLevel.ERROR: Color.RED,
+ LoggingLevel.CRITICAL: Color.BOLD + Color.RED,
+ }
+
+ def __init__(self, source: str, log_file: str = "logs/contwatch.log"):
+ self.source = source
+ self.log_file = log_file
+
+ # Setup standard logging for file output
+ self._file_logger = logging.getLogger(source)
+ if not self._file_logger.handlers:
+ self._file_logger.setLevel(logging.DEBUG)
+ handler = logging.FileHandler(self.log_file)
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+ handler.setFormatter(formatter)
+ self._file_logger.addHandler(handler)
+
+ self.info = self._generic_msg(LoggingLevel.INFO)
+ self.debug = self._generic_msg(LoggingLevel.DEBUG)
+ self.warning = self._generic_msg(LoggingLevel.WARNING)
+ self.error = self._generic_msg(LoggingLevel.ERROR)
+ self.critical = self._generic_msg(LoggingLevel.CRITICAL)
+
+ def _generic_msg(self, level: str):
+ def function(message: str, payload: dict = None):
+ color = self.level_colors[level]
+ color_print(f"[{level.upper()}] {self.source}: {message}", color)
+
+ log_method = getattr(self._file_logger, level.lower())
+ log_msg = f"{message} | Payload: {payload}" if payload else message
+ log_method(log_msg)
+
+ if payload:
+ for title, payload in payload.items():
+ color_print(f"payload.{title}: {payload}", Color.GREY)
+
+ return function
diff --git a/modules/core/actions/routines/helpers/__init__.py b/src/server/modules/models/__init__.py
similarity index 100%
rename from modules/core/actions/routines/helpers/__init__.py
rename to src/server/modules/models/__init__.py
diff --git a/src/server/modules/models/action.py b/src/server/modules/models/action.py
new file mode 100644
index 0000000..2bc4c6f
--- /dev/null
+++ b/src/server/modules/models/action.py
@@ -0,0 +1,12 @@
+from pony import orm
+
+from modules.database import db
+
+
+class Action(db.Entity):
+ """Database entity representing action configuration"""
+
+ name = orm.Required(str, index=True)
+ message = orm.Required(str)
+ widget_switches_on = orm.Set("WidgetSwitch", reverse="action_on")
+ widget_switches_off = orm.Set("WidgetSwitch", reverse="action_off")
diff --git a/src/server/modules/models/attribute.py b/src/server/modules/models/attribute.py
new file mode 100644
index 0000000..f0fdcbc
--- /dev/null
+++ b/src/server/modules/models/attribute.py
@@ -0,0 +1,43 @@
+from pony import orm
+
+from modules.database import db
+
+
+class Attribute(db.Entity):
+ """Database entity representing data attribute"""
+
+ name = orm.Required(str, index=True)
+ handler = orm.Required("Handler")
+ data_units = orm.Set("DataUnit")
+ data_stats = orm.Set("DataStat")
+ widget_tiles = orm.Set("WidgetTile")
+ widget_switches = orm.Set("WidgetSwitch")
+ enabled = orm.Required(bool)
+ unit = orm.Optional(str, nullable=True)
+ label = orm.Optional(str, nullable=True)
+ icon = orm.Optional(str, nullable=True)
+ order = orm.Optional(int, nullable=True)
+ rounding = orm.Optional(int, nullable=True)
+ color = orm.Optional(str, nullable=True)
+
+ # def to_json(self):
+ # return {}
+
+
+def get_by_id(a_id) -> Attribute | None:
+ """Returns attribute by id"""
+ return Attribute.get(id=a_id)
+
+
+def get_by_handler_and_name(handler, name) -> Attribute | None:
+ """Returns attribute by handler and name"""
+ return Attribute.get(handler=handler, name=name)
+
+
+def modify(handler, name, enabled=True) -> Attribute:
+ """Adds or modifies DataAttribute in database"""
+ existing_attribute = get_by_handler_and_name(handler, name)
+ if existing_attribute:
+ existing_attribute.enabled = enabled
+ return existing_attribute
+ return Attribute(handler=handler, name=name, enabled=enabled, order=Attribute.select().count())
diff --git a/src/server/modules/models/data_stat.py b/src/server/modules/models/data_stat.py
new file mode 100644
index 0000000..6e4ef0e
--- /dev/null
+++ b/src/server/modules/models/data_stat.py
@@ -0,0 +1,35 @@
+from datetime import date, datetime, time
+
+from pony import orm
+
+from modules.database import db
+
+
+class DataStat(db.Entity):
+ """Database entity representing data statistics"""
+
+ attribute = orm.Required("Attribute", index=True)
+ type = orm.Required(str)
+ value = orm.Required(float)
+ date = orm.Required(date, index=True)
+ time = orm.Required(time)
+
+ # def to_json(self):
+ # return {}
+
+
+def add(handler, attribute, stat_type, value) -> DataStat:
+ """Adds DataStat to database"""
+ now = datetime.now()
+ return DataStat(
+ attribute=attribute,
+ type=stat_type,
+ value=value,
+ date=now.date(),
+ time=now.time(),
+ )
+
+
+def get_by_type_and_date(attribute, stat_type, stat_date) -> DataStat | None:
+ """Return DataStat by attribute and date"""
+ return DataStat.get(attribute=attribute, type=stat_type, date=stat_date)
diff --git a/src/server/modules/models/data_unit.py b/src/server/modules/models/data_unit.py
new file mode 100644
index 0000000..46a0cb6
--- /dev/null
+++ b/src/server/modules/models/data_unit.py
@@ -0,0 +1,41 @@
+from datetime import date, time
+
+from pony import orm
+from pony.orm import composite_index
+
+from modules.database import db
+
+
+class DataUnit(db.Entity):
+ """Database entity representing data"""
+
+ handler = orm.Required("Handler")
+ # name = orm.Required(str, index=True)
+ attribute = orm.Required("Attribute", index=True)
+ value = orm.Required(float)
+ date = orm.Required(date, index=True)
+ time = orm.Required(time)
+ composite_index(attribute, date)
+
+ # def to_json(self):
+ # return {}
+
+
+def add(handler, attribute, value, timestamp) -> DataUnit:
+ """Adds DataUnit to database"""
+ return DataUnit(
+ handler=handler,
+ attribute=attribute,
+ value=value,
+ date=timestamp.date(),
+ time=timestamp.time(),
+ )
+
+
+def get_by_handler_id(handler_id) -> DataUnit | None:
+ """Returns DataUnit by handler id"""
+ return (
+ DataUnit.select(lambda unit: unit.handler.id == handler_id)
+ .order_by(orm.desc(DataUnit.date), orm.desc(DataUnit.time))
+ .limit(1)
+ )
diff --git a/src/server/modules/models/handler.py b/src/server/modules/models/handler.py
new file mode 100644
index 0000000..2709602
--- /dev/null
+++ b/src/server/modules/models/handler.py
@@ -0,0 +1,37 @@
+from pony import orm
+
+from modules.database import db
+from modules.handlers.abstract_handler import AbstractHandler
+from modules.models.attribute import Attribute
+from modules.models.data_unit import DataUnit
+
+
+class Handler(db.Entity):
+ """Database entity representing handler configuration"""
+
+ type = orm.Required(str)
+ options = orm.Required(orm.Json)
+ enabled = orm.Required(bool)
+ data = orm.Set(DataUnit)
+ attributes = orm.Set(Attribute)
+ # events = orm.Set("EventUnit")
+
+
+def get_all() -> list[Handler]:
+ """Returns all handlers"""
+ return Handler.select()
+
+
+def add(handler: AbstractHandler, h_id=0) -> Handler:
+ """Adds handler to database"""
+ return Handler(
+ id=h_id if h_id else None,
+ type=handler.type,
+ options=handler.options,
+ enabled=True,
+ )
+
+
+def get_by_id(h_id) -> Handler | None:
+ """Returns handler by id"""
+ return Handler.get(id=h_id)
diff --git a/src/server/modules/models/logging_message.py b/src/server/modules/models/logging_message.py
new file mode 100644
index 0000000..707c635
--- /dev/null
+++ b/src/server/modules/models/logging_message.py
@@ -0,0 +1,24 @@
+from datetime import date, datetime, time
+
+from pony import orm
+
+from modules.database import db
+
+
+class LoggingMessage(db.Entity):
+ """Logging message entity"""
+
+ source = orm.Required(str)
+ level = orm.Required(int)
+ message = orm.Required(str)
+ payload = orm.Optional(orm.Json, nullable=True)
+ date = orm.Required(date, index=True)
+ time = orm.Required(time)
+
+
+def add(source, level, message, payload) -> LoggingMessage:
+ """Adds message to database"""
+ now = datetime.now()
+ return LoggingMessage(
+ source=source, level=level, message=message, payload=payload, date=now.date(), time=now.time()
+ )
diff --git a/src/server/modules/models/settings.py b/src/server/modules/models/settings.py
new file mode 100644
index 0000000..dcd0164
--- /dev/null
+++ b/src/server/modules/models/settings.py
@@ -0,0 +1,25 @@
+from pony import orm
+
+from modules.database import db
+
+
+class Settings(db.Entity):
+ """Database entity representing settings"""
+
+ user = orm.Required(int, index=True)
+ actions_node_map = orm.Optional(orm.Json)
+
+
+def get_settings(user_id: int) -> Settings:
+ """Returns settings for user"""
+ return Settings.get(user=user_id)
+
+
+def set_settings(user_id: int, actions_node_map: dict) -> Settings:
+ """Sets settings for user"""
+ settings = Settings.get(user=user_id)
+ if settings is None:
+ settings = Settings(user=user_id, actions_node_map=actions_node_map)
+ else:
+ settings.actions_node_map = actions_node_map
+ return settings
diff --git a/src/server/modules/models/unit.py b/src/server/modules/models/unit.py
new file mode 100644
index 0000000..b4818c1
--- /dev/null
+++ b/src/server/modules/models/unit.py
@@ -0,0 +1,11 @@
+from pony import orm
+
+from modules.database import db
+
+
+class Unit(db.Entity):
+ """Database entity representing unit"""
+
+ name = orm.Required(str, index=True)
+ base_unit = orm.Optional(str, index=True, nullable=True)
+ base_ratio = orm.Optional(float, nullable=True)
diff --git a/src/server/modules/models/widget_switch.py b/src/server/modules/models/widget_switch.py
new file mode 100644
index 0000000..b0d0a0f
--- /dev/null
+++ b/src/server/modules/models/widget_switch.py
@@ -0,0 +1,15 @@
+from pony import orm
+
+from modules.database import db
+from modules.models.action import Action
+
+
+class WidgetSwitch(db.Entity):
+ """Database entity representing widget switch"""
+
+ name = orm.Optional(str, index=True, nullable=True)
+ icon = orm.Optional(str, nullable=True)
+ attribute = orm.Required("Attribute")
+ attribute_compare = orm.Optional(str, nullable=True)
+ action_on = orm.Optional(Action, nullable=True)
+ action_off = orm.Optional(Action, nullable=True)
diff --git a/src/server/modules/models/widget_tile.py b/src/server/modules/models/widget_tile.py
new file mode 100644
index 0000000..a8e3f85
--- /dev/null
+++ b/src/server/modules/models/widget_tile.py
@@ -0,0 +1,19 @@
+from pony import orm
+
+from modules.database import db
+
+
+class WidgetTile(db.Entity):
+ """Database entity representing widget tile"""
+
+ attribute = orm.Required("Attribute")
+
+
+def get_by_id(w_id) -> WidgetTile | None:
+ """Returns widget tile by id"""
+ return WidgetTile.get(id=w_id)
+
+
+def get_all() -> list[WidgetTile]:
+ """Returns all widget tiles"""
+ return list(WidgetTile.select())
diff --git a/src/server/modules/utils/__init__.py b/src/server/modules/utils/__init__.py
new file mode 100644
index 0000000..1e61717
--- /dev/null
+++ b/src/server/modules/utils/__init__.py
@@ -0,0 +1,6 @@
+from .color_print import *
+from .context import *
+from .enums import *
+from .int_list_converter import *
+from .modules_registrator import *
+from .tools import *
diff --git a/src/server/modules/utils/color_print.py b/src/server/modules/utils/color_print.py
new file mode 100644
index 0000000..c02dfba
--- /dev/null
+++ b/src/server/modules/utils/color_print.py
@@ -0,0 +1,14 @@
+class Color:
+ GREY = "\033[90m"
+ RED = "\033[91m"
+ GREEN = "\033[92m"
+ ORANGE = "\033[93m"
+ BLUE = "\033[94m"
+ PINK = "\033[95m"
+ END = "\033[0m"
+ BOLD = "\033[1m"
+ UNDERLINE = "\033[4m"
+
+
+def color_print(message: str | dict, color: str = Color.END):
+ print(color + str(message) + Color.END)
diff --git a/src/server/modules/utils/context.py b/src/server/modules/utils/context.py
new file mode 100644
index 0000000..9823b1a
--- /dev/null
+++ b/src/server/modules/utils/context.py
@@ -0,0 +1,14 @@
+class Context:
+ """Class for passing references to constructors"""
+
+ def __init__(self, manager, socketio=None):
+ self._manager = manager
+ self._socketio = socketio
+
+ @property
+ def manager(self):
+ return self._manager
+
+ @property
+ def socketio(self):
+ return self._socketio
diff --git a/src/server/modules/utils/enums.py b/src/server/modules/utils/enums.py
new file mode 100644
index 0000000..22ec4c1
--- /dev/null
+++ b/src/server/modules/utils/enums.py
@@ -0,0 +1,5 @@
+class StatusCode:
+ OK = 200
+ CREATED = 201
+ NOT_FOUND = 404
+ BAD_REQUEST = 400
diff --git a/src/server/modules/utils/int_list_converter.py b/src/server/modules/utils/int_list_converter.py
new file mode 100644
index 0000000..d019fd8
--- /dev/null
+++ b/src/server/modules/utils/int_list_converter.py
@@ -0,0 +1,11 @@
+from werkzeug.routing import BaseConverter
+
+
+class IntListConverter(BaseConverter):
+ regex = r"\d+(?:,\d+)*,?"
+
+ def to_python(self, value):
+ return [int(x) for x in value.split(",")]
+
+ def to_url(self, value):
+ return ",".join(str(x) for x in value)
diff --git a/modules/tools/modules_registrator.py b/src/server/modules/utils/modules_registrator.py
similarity index 81%
rename from modules/tools/modules_registrator.py
rename to src/server/modules/utils/modules_registrator.py
index b36262c..6bcc5f6 100644
--- a/modules/tools/modules_registrator.py
+++ b/src/server/modules/utils/modules_registrator.py
@@ -1,5 +1,5 @@
class ModulesRegistrator:
- """Handles modules registration"""
+ """Handles modules registration and their exit on shutdown"""
def __init__(self):
self.registered_modules = []
diff --git a/src/server/modules/utils/tools.py b/src/server/modules/utils/tools.py
new file mode 100644
index 0000000..36acfc8
--- /dev/null
+++ b/src/server/modules/utils/tools.py
@@ -0,0 +1,74 @@
+import inspect
+
+from time import time
+
+
+def this_name():
+ """Returns the name of the function that called this function"""
+ return inspect.stack()[1].function
+
+
+def get_current_seconds():
+ """Returns result of time() as integer"""
+ return int(time())
+
+
+# TODO: Refactor to not use result as an argument
+# TODO: Unit test
+def linearize(input_json, result=None, current_branch=()):
+ if result is None:
+ result = {}
+ for attribute in input_json:
+ if isinstance(input_json[attribute], dict):
+ new_branch = list(current_branch)
+ new_branch.append(attribute)
+ linearize(input_json[attribute], result, new_branch)
+ elif isinstance(input_json[attribute], list):
+ for index, item in enumerate(input_json[attribute]):
+ linearize({attribute: {str(index): item}}, result, current_branch)
+ else:
+ branch = list(current_branch)
+ branch.append(attribute)
+ result["/".join(branch)] = input_json[attribute]
+ return result
+
+
+def get_nested_attribute(json, attributes_row):
+ attributes = attributes_row.split("/")
+ attributes.reverse()
+ result = json
+ while attributes:
+ attribute = attributes.pop()
+ if attribute in result:
+ result = result[attribute]
+ else:
+ return
+ return result
+
+
+def parse_config(config, handler_class):
+ parsed_config = {}
+
+ for field in config:
+ if field in handler_class.config_fields:
+ field_type = handler_class.config_fields[field][0]
+ value = config[field]
+ # if not value:
+ # continue
+ if field_type in ["int", "handlerInstance", "workflowInstance"]:
+ value = int(value)
+ elif field_type == "float":
+ value = float(value)
+ elif field_type == "bool":
+ value = bool(value)
+ parsed_config[field] = value
+
+ for field in handler_class.config_fields:
+ if len(handler_class.config_fields[field]) > 2 and field not in parsed_config:
+ parsed_config[field] = handler_class.config_fields[field][2]
+
+ if handler_class.config_fields[field][0] == "bool":
+ if f"_{field}_" not in config:
+ parsed_config[field] = False
+
+ return parsed_config
diff --git a/tsconfig.json b/tsconfig.json
deleted file mode 100644
index fb6f57f..0000000
--- a/tsconfig.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "compilerOptions": {
- "module": "es2022",
- "target": "es6",
- "noImplicitAny": true,
- "removeComments": true,
- "preserveConstEnums": true,
- "esModuleInterop": true,
- "sourceMap": true,
- "moduleResolution": "Node",
- "outDir": "modules/web_server/static/js"
- },
- "include": [
- "modules/web_server/src/*"
- ],
- "exclude": [
- "webpack.config.ts"
- ]
-}
diff --git a/webpack.config.ts b/webpack.config.ts
deleted file mode 100644
index 919febe..0000000
--- a/webpack.config.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import path from "path";
-import { fileURLToPath } from 'url';
-import { Configuration } from "webpack";
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = path.dirname(__filename);
-
-const config: Configuration = {
- entry: "./modules/web_server/src/main.ts",
- mode: "development",
- module: {
- rules: [
- {
- test: /\.tsx?$/,
- exclude: /node_modules/,
- use: {
- loader: "babel-loader",
- options: {
- presets: ["@babel/preset-env", "@babel/preset-typescript"],
- },
- },
- }
- ]
- },
- resolve: {
- extensions: [".tsx", ".ts", ".js"]
- },
- output: {
- path: path.resolve(__dirname, "modules/web_server/static/js"),
- filename: "bundle.js",
- }
-};
-
-export default config;
\ No newline at end of file