diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..37c4af5f --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +**/node_modules/** +**/.next/** +tsconfig.json +*.min.js diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..0d08e261 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "github-actions" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/docs_build.yml b/.github/workflows/docs_build.yml index ee5d7531..cb67e689 100644 --- a/.github/workflows/docs_build.yml +++ b/.github/workflows/docs_build.yml @@ -18,10 +18,10 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.5 - name: Install dependencies, build docs and coverage report run: python3 -m pip install --upgrade pip && python3 -m pip install poetry - - uses: actions/setup-python@v5.0.0 + - uses: actions/setup-python@v5.1.0 with: python-version: '3.9' cache: 'poetry' diff --git a/.github/workflows/docs_deploy.yml b/.github/workflows/docs_deploy.yml index 505418d0..1b2a2893 100644 --- a/.github/workflows/docs_deploy.yml +++ b/.github/workflows/docs_deploy.yml @@ -21,12 +21,12 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.5 with: submodules: 'true' - name: Install dependencies, build docs and coverage report run: python3 -m pip install --upgrade pip && python3 -m pip install poetry - - uses: actions/setup-python@v5.0.0 + - uses: actions/setup-python@v5.1.0 with: python-version: '3.9' cache: 'poetry' diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml index 181274a3..473528c0 100644 --- a/.github/workflows/integration_tests.yaml +++ b/.github/workflows/integration_tests.yaml @@ -36,14 +36,14 @@ jobs: integration-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.5 - name: Install poetry run: python3 -m pip install --upgrade pip && python3 -m pip install poetry - - uses: actions/setup-python@v5.0.0 + - uses: actions/setup-python@v5.1.0 with: python-version: '3.9' - name: Setup redis - uses: supercharge/redis-github-action@1.2.0 + uses: supercharge/redis-github-action@1.8.0 with: redis-version: 7.2.4 - name: Setup MongoDB @@ -56,13 +56,30 @@ jobs: source $(poetry env info --path)/bin/activate poetry install --with docs,test coverage run -m pytest florist/tests/integration && coverage xml && coverage report -m -# - name: Upload coverage to Codecov -# uses: Wandalen/wretry.action@v1.4.4 + - name: Upload python coverage to Codecov + uses: Wandalen/wretry.action@v3.4.0 + with: + action: codecov/codecov-action@v4.0.1 + with: | + token: ${{ secrets.CODECOV_TOKEN }} + name: codecov-umbrella + fail_ci_if_error: true + attempt_limit: 5 + attempt_delay: 30000 + + # TODO enable this once we have integration tests for the UI +# - name: Setup yarn +# uses: mskelton/setup-yarn@v1 +# - name: Install nextjs dependencies and check code +# run: | +# yarn +# yarn integration-test +# - name: Upload js coverage to Codecov +# uses: Wandalen/wretry.action@v3.4.0 # with: # action: codecov/codecov-action@v4.0.1 # with: | # token: ${{ secrets.CODECOV_TOKEN }} -# file: ./coverage.xml # name: codecov-umbrella # fail_ci_if_error: true # attempt_limit: 5 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index da22deb0..ef36c61a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,16 +12,16 @@ jobs: run: | sudo apt-get update sudo apt-get install libcurl4-openssl-dev libssl-dev - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.5 - name: Install poetry run: python3 -m pip install --upgrade pip && python3 -m pip install poetry - - uses: actions/setup-python@v5.0.0 + - uses: actions/setup-python@v5.1.0 with: python-version: '3.9' - name: Build package run: poetry build - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/static_code_checks.yaml b/.github/workflows/static_code_checks.yaml index a556550a..ded98bc7 100644 --- a/.github/workflows/static_code_checks.yaml +++ b/.github/workflows/static_code_checks.yaml @@ -26,18 +26,21 @@ jobs: run-code-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.5 - name: Install and configure Poetry uses: snok/install-poetry@v1 with: virtualenvs-create: true virtualenvs-in-project: true - - uses: actions/setup-python@v5.0.0 + - uses: actions/setup-python@v5.1.0 with: python-version: '3.9' cache: 'poetry' + - name: Setup yarn + uses: mskelton/setup-yarn@v1 - name: Install dependencies and check code run: | + yarn poetry env use '3.9' source .venv/bin/activate poetry install --with test --all-extras diff --git a/.github/workflows/unit_tests.yaml b/.github/workflows/unit_tests.yaml index cfe6b1d0..4eab14e8 100644 --- a/.github/workflows/unit_tests.yaml +++ b/.github/workflows/unit_tests.yaml @@ -36,26 +36,42 @@ jobs: unit-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4.1.1 + - uses: actions/checkout@v4.1.5 - name: Install poetry run: python3 -m pip install --upgrade pip && python3 -m pip install poetry - - uses: actions/setup-python@v5.0.0 + - uses: actions/setup-python@v5.1.0 with: python-version: '3.9' - - name: Install dependencies and check code + cache: 'poetry' + - name: Install python dependencies and check code run: | poetry env use '3.9' source $(poetry env info --path)/bin/activate poetry install --with docs,test coverage run -m pytest florist/tests/unit && coverage xml && coverage report -m -# - name: Upload coverage to Codecov -# uses: Wandalen/wretry.action@v1.4.4 -# with: -# action: codecov/codecov-action@v4.0.1 -# with: | -# token: ${{ secrets.CODECOV_TOKEN }} -# file: ./coverage.xml -# name: codecov-umbrella -# fail_ci_if_error: true -# attempt_limit: 5 -# attempt_delay: 30000 + - name: Upload python coverage to Codecov + uses: Wandalen/wretry.action@v3.4.0 + with: + action: codecov/codecov-action@v4.0.1 + with: | + token: ${{ secrets.CODECOV_TOKEN }} + name: codecov-umbrella + fail_ci_if_error: true + attempt_limit: 5 + attempt_delay: 30000 + - name: Setup yarn + uses: mskelton/setup-yarn@v1 + - name: Install nextjs dependencies and check code + run: | + yarn + yarn unit-test + - name: Upload js coverage to Codecov + uses: Wandalen/wretry.action@v3.4.0 + with: + action: codecov/codecov-action@v4.0.1 + with: | + token: ${{ secrets.CODECOV_TOKEN }} + name: codecov-umbrella + fail_ci_if_error: true + attempt_limit: 5 + attempt_delay: 30000 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c577a37f..be4c5483 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 # Use the ref you want to point at + rev: v4.6.0 # Use the ref you want to point at hooks: - id: trailing-whitespace - id: check-ast @@ -15,8 +15,8 @@ repos: - id: check-yaml - id: check-toml - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.2.2' + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: 'v0.4.3' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -25,7 +25,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 + rev: v1.10.0 hooks: - id: mypy entry: python3 -m mypy --config-file pyproject.toml @@ -34,11 +34,31 @@ repos: exclude: "florist/tests/" - repo: https://github.com/nbQA-dev/nbQA - rev: 1.7.1 + rev: 1.8.5 hooks: - id: nbqa-ruff args: [--fix, --exit-non-zero-on-fix] + - repo: local + hooks: + - id: nextjs-lint + name: nextjs-lint + entry: yarn lint-gh-action florist + files: "florist/app" + language: system + + - repo: local + hooks: + - id: prettier-js-format + name: prettier-js-format + entry: yarn prettier + files: "florist/app" + language: node + types: [javascript] + additional_dependencies: + - yarn + - prettier + - repo: local hooks: - id: doctest @@ -46,3 +66,32 @@ repos: entry: python3 -m doctest -o NORMALIZE_WHITESPACE files: "(^florist/api/|florist/tests/api/)" language: system + + - repo: local + hooks: + - id: pytest-unit + name: pytest-unit + entry: python -m pytest florist/tests/unit + language: system + pass_filenames: false + always_run: true + + - repo: local + hooks: + - id: nextjs-unit + name: nextjs-unit + entry: yarn unit-test + language: system + pass_filenames: false + always_run: true + +ci: + autofix_commit_msg: | + [pre-commit.ci] Add auto fixes from pre-commit.com hooks + for more information, see https://pre-commit.ci + autofix_prs: true + autoupdate_branch: '' + autoupdate_commit_msg: '[pre-commit.ci] pre-commit autoupdate' + autoupdate_schedule: weekly + skip: [pytest-unit,nextjs-unit,doctest,mypy,nextjs-lint] + submodules: false diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..170dfa09 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +florist/app/assets/css/material-dashboard.css +*.min.js diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..0a02bcef --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "tabWidth": 4 +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e629821..bdc62ef0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,7 +37,7 @@ using [Homebrew](https://brew.sh/): brew install yarn ``` -Then install the project dependencies: +Then install the project dependencies in development mode: ```shell yarn ``` @@ -55,11 +55,16 @@ yarn dev ## Running the tests -To run the unit tests, simply execute: +To run the python unit tests, simply execute: ```shell pytest florist/tests/unit ``` +To run the nextjs unit tests, simply execute: +```shell +yarn unit-test +``` + To run the integration tests, first make sure you: - Have a Redis server running on your local machine on port 6379 by following [these instructions](README.md#start-servers-redis-instance). - Have a MongoDB server running on your local machine on port 27017 by following [these instructions](README.md#start-mongodbs-instance). diff --git a/README.md b/README.md index c2c6f407..eb08da5b 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,9 @@ using [Homebrew](https://brew.sh/): brew install yarn ``` -Then install the project dependencies: +Then install the project dependencies in production mode: ```shell -yarn +yarn --prod ``` ### Pulling Redis' Docker diff --git a/florist/api/client.py b/florist/api/client.py index 27529e05..cfe512f9 100644 --- a/florist/api/client.py +++ b/florist/api/client.py @@ -1,4 +1,5 @@ """FLorist client FastAPI endpoints.""" + import json import logging import uuid diff --git a/florist/api/clients/common.py b/florist/api/clients/common.py index bdc77f16..6a9e79ea 100644 --- a/florist/api/clients/common.py +++ b/florist/api/clients/common.py @@ -1,4 +1,5 @@ """Common functions and definitions for clients.""" + from enum import Enum from typing import List diff --git a/florist/api/clients/mnist.py b/florist/api/clients/mnist.py index 575fc27a..ca6093f8 100644 --- a/florist/api/clients/mnist.py +++ b/florist/api/clients/mnist.py @@ -1,4 +1,5 @@ """Implementation of the MNIST client and model.""" + from typing import Tuple import torch diff --git a/florist/api/db/entities.py b/florist/api/db/entities.py index c33ab90a..8190a303 100644 --- a/florist/api/db/entities.py +++ b/florist/api/db/entities.py @@ -1,4 +1,5 @@ """Definitions for the MongoDB database entities.""" + import json import uuid from enum import Enum @@ -95,7 +96,7 @@ class Config: "server_info": '{"n_server_rounds": 3, "batch_size": 8}', "redis_host": "localhost", "redis_port": "6879", - "client_info": [ + "clients_info": [ { "client": "MNIST", "service_address": "locahost:8081", diff --git a/florist/api/launchers/local.py b/florist/api/launchers/local.py index 1681f5e1..a085e719 100644 --- a/florist/api/launchers/local.py +++ b/florist/api/launchers/local.py @@ -1,4 +1,5 @@ """Launcher functions for local clients and servers.""" + import logging import sys import time diff --git a/florist/api/models/mnist.py b/florist/api/models/mnist.py index f1d3d616..eb344a2c 100644 --- a/florist/api/models/mnist.py +++ b/florist/api/models/mnist.py @@ -1,4 +1,5 @@ """Definitions for the MNIST model.""" + from typing import List import torch diff --git a/florist/api/monitoring/logs.py b/florist/api/monitoring/logs.py index ed8bdfb6..64c0e2f1 100644 --- a/florist/api/monitoring/logs.py +++ b/florist/api/monitoring/logs.py @@ -1,4 +1,5 @@ """General functions and definitions for monitoring.""" + from pathlib import Path diff --git a/florist/api/monitoring/metrics.py b/florist/api/monitoring/metrics.py index f03afbf8..91631b1d 100644 --- a/florist/api/monitoring/metrics.py +++ b/florist/api/monitoring/metrics.py @@ -1,4 +1,5 @@ """Classes for the instrumentation of metrics reporting from clients and servers.""" + import json import time from logging import DEBUG, Logger diff --git a/florist/api/routes/server/job.py b/florist/api/routes/server/job.py index d8f6e5ae..fc844a38 100644 --- a/florist/api/routes/server/job.py +++ b/florist/api/routes/server/job.py @@ -1,4 +1,5 @@ """FastAPI routes for the job.""" + from json import JSONDecodeError from typing import Any, Dict, List diff --git a/florist/api/routes/server/status.py b/florist/api/routes/server/status.py index 4d13882f..02ccce8f 100644 --- a/florist/api/routes/server/status.py +++ b/florist/api/routes/server/status.py @@ -1,4 +1,5 @@ """FastAPI routes for checking server status.""" + import json import logging diff --git a/florist/api/routes/server/training.py b/florist/api/routes/server/training.py index c475c2a8..8c3031fb 100644 --- a/florist/api/routes/server/training.py +++ b/florist/api/routes/server/training.py @@ -1,4 +1,5 @@ """FastAPI routes for training.""" + import logging from json import JSONDecodeError from typing import List diff --git a/florist/api/server.py b/florist/api/server.py index 040e3eb1..fbb388d7 100644 --- a/florist/api/server.py +++ b/florist/api/server.py @@ -1,4 +1,5 @@ """FLorist server FastAPI endpoints and routes.""" + from contextlib import asynccontextmanager from typing import Any, AsyncGenerator diff --git a/florist/api/servers/common.py b/florist/api/servers/common.py index 23f679fe..117e7408 100644 --- a/florist/api/servers/common.py +++ b/florist/api/servers/common.py @@ -1,4 +1,5 @@ """Common functions and definitions for servers.""" + from enum import Enum from typing import List diff --git a/florist/api/servers/launch.py b/florist/api/servers/launch.py index 2971f916..7122d5c5 100644 --- a/florist/api/servers/launch.py +++ b/florist/api/servers/launch.py @@ -1,4 +1,5 @@ """Functions and definitions to launch local servers.""" + import uuid from functools import partial from multiprocessing import Process diff --git a/florist/api/servers/utils.py b/florist/api/servers/utils.py index d64f1618..4470ecd7 100644 --- a/florist/api/servers/utils.py +++ b/florist/api/servers/utils.py @@ -1,4 +1,5 @@ """Utilities functions and definitions for starting a server.""" + from functools import partial from typing import Callable, Dict, Union diff --git a/florist/app/assets/css/nucleo-icons.css b/florist/app/assets/css/nucleo-icons.css index b040a79a..5fb7b5a2 100644 --- a/florist/app/assets/css/nucleo-icons.css +++ b/florist/app/assets/css/nucleo-icons.css @@ -5,48 +5,53 @@ License - nucleoapp.com/license/ -------------------------------- */ @font-face { - font-family: 'NucleoIcons'; - src: url('../fonts/nucleo-icons.eot'); - src: url('../fonts/nucleo-icons.eot') format('embedded-opentype'), url('../fonts/nucleo-icons.woff2') format('woff2'), url('../fonts/nucleo-icons.woff') format('woff'), url('../fonts/nucleo-icons.ttf') format('truetype'), url('../fonts/nucleo-icons.svg') format('svg'); - font-weight: normal; - font-style: normal; + font-family: "NucleoIcons"; + src: url("../fonts/nucleo-icons.eot"); + src: + url("../fonts/nucleo-icons.eot") format("embedded-opentype"), + url("../fonts/nucleo-icons.woff2") format("woff2"), + url("../fonts/nucleo-icons.woff") format("woff"), + url("../fonts/nucleo-icons.ttf") format("truetype"), + url("../fonts/nucleo-icons.svg") format("svg"); + font-weight: normal; + font-style: normal; } /*------------------------ base class definition -------------------------*/ .ni { - display: inline-block; - font: normal normal normal 14px/1 NucleoIcons; - font-size: inherit; - text-rendering: auto; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + display: inline-block; + font: normal normal normal 14px/1 NucleoIcons; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } /*------------------------ change icon size -------------------------*/ .ni-lg { - font-size: 1.33333333em; - line-height: 0.75em; - vertical-align: -15%; + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; } .ni-2x { - font-size: 2em; + font-size: 2em; } .ni-3x { - font-size: 3em; + font-size: 3em; } .ni-4x { - font-size: 4em; + font-size: 4em; } .ni-5x { - font-size: 5em; + font-size: 5em; } /*---------------------------------- @@ -54,139 +59,139 @@ License - nucleoapp.com/license/ -----------------------------------*/ .ni.square, .ni.circle { - padding: 0.33333333em; - vertical-align: -16%; - background-color: #eee; + padding: 0.33333333em; + vertical-align: -16%; + background-color: #eee; } .ni.circle { - border-radius: 50%; + border-radius: 50%; } /*------------------------ list icons -------------------------*/ .ni-ul { - padding-left: 0; - margin-left: 2.14285714em; - list-style-type: none; + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; } -.ni-ul>li { - position: relative; +.ni-ul > li { + position: relative; } -.ni-ul>li>.ni { - position: absolute; - left: -1.57142857em; - top: 0.14285714em; - text-align: center; +.ni-ul > li > .ni { + position: absolute; + left: -1.57142857em; + top: 0.14285714em; + text-align: center; } -.ni-ul>li>.ni.lg { - top: 0; - left: -1.35714286em; +.ni-ul > li > .ni.lg { + top: 0; + left: -1.35714286em; } -.ni-ul>li>.ni.circle, -.ni-ul>li>.ni.square { - top: -0.19047619em; - left: -1.9047619em; +.ni-ul > li > .ni.circle, +.ni-ul > li > .ni.square { + top: -0.19047619em; + left: -1.9047619em; } /*------------------------ spinning icons -------------------------*/ .ni.spin { - -webkit-animation: nc-spin 2s infinite linear; - -moz-animation: nc-spin 2s infinite linear; - animation: nc-spin 2s infinite linear; + -webkit-animation: nc-spin 2s infinite linear; + -moz-animation: nc-spin 2s infinite linear; + animation: nc-spin 2s infinite linear; } @-webkit-keyframes nc-spin { - 0% { - -webkit-transform: rotate(0deg); - } + 0% { + -webkit-transform: rotate(0deg); + } - 100% { - -webkit-transform: rotate(360deg); - } + 100% { + -webkit-transform: rotate(360deg); + } } @-moz-keyframes nc-spin { - 0% { - -moz-transform: rotate(0deg); - } + 0% { + -moz-transform: rotate(0deg); + } - 100% { - -moz-transform: rotate(360deg); - } + 100% { + -moz-transform: rotate(360deg); + } } @keyframes nc-spin { - 0% { - -webkit-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -ms-transform: rotate(0deg); - -o-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -webkit-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -o-transform: rotate(360deg); - transform: rotate(360deg); - } + 0% { + -webkit-transform: rotate(0deg); + -moz-transform: rotate(0deg); + -ms-transform: rotate(0deg); + -o-transform: rotate(0deg); + transform: rotate(0deg); + } + + 100% { + -webkit-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -o-transform: rotate(360deg); + transform: rotate(360deg); + } } /*------------------------ rotated/flipped icons -------------------------*/ .ni.rotate-90 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); - -webkit-transform: rotate(90deg); - -moz-transform: rotate(90deg); - -ms-transform: rotate(90deg); - -o-transform: rotate(90deg); - transform: rotate(90deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); + -o-transform: rotate(90deg); + transform: rotate(90deg); } .ni.rotate-180 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); - -webkit-transform: rotate(180deg); - -moz-transform: rotate(180deg); - -ms-transform: rotate(180deg); - -o-transform: rotate(180deg); - transform: rotate(180deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -ms-transform: rotate(180deg); + -o-transform: rotate(180deg); + transform: rotate(180deg); } .ni.rotate-270 { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); - -webkit-transform: rotate(270deg); - -moz-transform: rotate(270deg); - -ms-transform: rotate(270deg); - -o-transform: rotate(270deg); - transform: rotate(270deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + -webkit-transform: rotate(270deg); + -moz-transform: rotate(270deg); + -ms-transform: rotate(270deg); + -o-transform: rotate(270deg); + transform: rotate(270deg); } .ni.flip-y { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0); - -webkit-transform: scale(-1, 1); - -moz-transform: scale(-1, 1); - -ms-transform: scale(-1, 1); - -o-transform: scale(-1, 1); - transform: scale(-1, 1); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0); + -webkit-transform: scale(-1, 1); + -moz-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + -o-transform: scale(-1, 1); + transform: scale(-1, 1); } .ni.flip-x { - filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); - -webkit-transform: scale(1, -1); - -moz-transform: scale(1, -1); - -ms-transform: scale(1, -1); - -o-transform: scale(1, -1); - transform: scale(1, -1); + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -webkit-transform: scale(1, -1); + -moz-transform: scale(1, -1); + -ms-transform: scale(1, -1); + -o-transform: scale(1, -1); + transform: scale(1, -1); } /*------------------------ @@ -194,404 +199,403 @@ License - nucleoapp.com/license/ -------------------------*/ .ni-active-40::before { - content: "\ea02"; + content: "\ea02"; } .ni-air-baloon::before { - content: "\ea03"; + content: "\ea03"; } .ni-album-2::before { - content: "\ea04"; + content: "\ea04"; } .ni-align-center::before { - content: "\ea05"; + content: "\ea05"; } .ni-align-left-2::before { - content: "\ea06"; + content: "\ea06"; } .ni-ambulance::before { - content: "\ea07"; + content: "\ea07"; } .ni-app::before { - content: "\ea08"; + content: "\ea08"; } .ni-archive-2::before { - content: "\ea09"; + content: "\ea09"; } .ni-atom::before { - content: "\ea0a"; + content: "\ea0a"; } .ni-badge::before { - content: "\ea0b"; + content: "\ea0b"; } .ni-bag-17::before { - content: "\ea0c"; + content: "\ea0c"; } .ni-basket::before { - content: "\ea0d"; + content: "\ea0d"; } .ni-bell-55::before { - content: "\ea0e"; + content: "\ea0e"; } .ni-bold-down::before { - content: "\ea0f"; + content: "\ea0f"; } .ni-bold-left::before { - content: "\ea10"; + content: "\ea10"; } .ni-bold-right::before { - content: "\ea11"; + content: "\ea11"; } .ni-bold-up::before { - content: "\ea12"; + content: "\ea12"; } .ni-bold::before { - content: "\ea13"; + content: "\ea13"; } .ni-book-bookmark::before { - content: "\ea14"; + content: "\ea14"; } .ni-books::before { - content: "\ea15"; + content: "\ea15"; } .ni-box-2::before { - content: "\ea16"; + content: "\ea16"; } .ni-briefcase-24::before { - content: "\ea17"; + content: "\ea17"; } .ni-building::before { - content: "\ea18"; + content: "\ea18"; } .ni-bulb-61::before { - content: "\ea19"; + content: "\ea19"; } .ni-bullet-list-67::before { - content: "\ea1a"; + content: "\ea1a"; } .ni-bus-front-12::before { - content: "\ea1b"; + content: "\ea1b"; } .ni-button-pause::before { - content: "\ea1c"; + content: "\ea1c"; } .ni-button-play::before { - content: "\ea1d"; + content: "\ea1d"; } .ni-button-power::before { - content: "\ea1e"; + content: "\ea1e"; } .ni-calendar-grid-58::before { - content: "\ea1f"; + content: "\ea1f"; } .ni-camera-compact::before { - content: "\ea20"; + content: "\ea20"; } .ni-caps-small::before { - content: "\ea21"; + content: "\ea21"; } .ni-cart::before { - content: "\ea22"; + content: "\ea22"; } .ni-chart-bar-32::before { - content: "\ea23"; + content: "\ea23"; } .ni-chart-pie-35::before { - content: "\ea24"; + content: "\ea24"; } .ni-chat-round::before { - content: "\ea25"; + content: "\ea25"; } .ni-check-bold::before { - content: "\ea26"; + content: "\ea26"; } .ni-circle-08::before { - content: "\ea27"; + content: "\ea27"; } .ni-cloud-download-95::before { - content: "\ea28"; + content: "\ea28"; } .ni-cloud-upload-96::before { - content: "\ea29"; + content: "\ea29"; } .ni-compass-04::before { - content: "\ea2a"; + content: "\ea2a"; } .ni-controller::before { - content: "\ea2b"; + content: "\ea2b"; } .ni-credit-card::before { - content: "\ea2c"; + content: "\ea2c"; } .ni-curved-next::before { - content: "\ea2d"; + content: "\ea2d"; } .ni-delivery-fast::before { - content: "\ea2e"; + content: "\ea2e"; } .ni-diamond::before { - content: "\ea2f"; + content: "\ea2f"; } .ni-email-83::before { - content: "\ea30"; + content: "\ea30"; } .ni-fat-add::before { - content: "\ea31"; + content: "\ea31"; } .ni-fat-delete::before { - content: "\ea32"; + content: "\ea32"; } .ni-fat-remove::before { - content: "\ea33"; + content: "\ea33"; } .ni-favourite-28::before { - content: "\ea34"; + content: "\ea34"; } .ni-folder-17::before { - content: "\ea35"; + content: "\ea35"; } .ni-glasses-2::before { - content: "\ea36"; + content: "\ea36"; } .ni-hat-3::before { - content: "\ea37"; + content: "\ea37"; } .ni-headphones::before { - content: "\ea38"; + content: "\ea38"; } .ni-html5::before { - content: "\ea39"; + content: "\ea39"; } .ni-istanbul::before { - content: "\ea3a"; + content: "\ea3a"; } .ni-key-25::before { - content: "\ea3b"; + content: "\ea3b"; } .ni-laptop::before { - content: "\ea3c"; + content: "\ea3c"; } .ni-like-2::before { - content: "\ea3d"; + content: "\ea3d"; } .ni-lock-circle-open::before { - content: "\ea3e"; + content: "\ea3e"; } .ni-map-big::before { - content: "\ea3f"; + content: "\ea3f"; } .ni-mobile-button::before { - content: "\ea40"; + content: "\ea40"; } .ni-money-coins::before { - content: "\ea41"; + content: "\ea41"; } .ni-note-03::before { - content: "\ea42"; + content: "\ea42"; } .ni-notification-70::before { - content: "\ea43"; + content: "\ea43"; } .ni-palette::before { - content: "\ea44"; + content: "\ea44"; } .ni-paper-diploma::before { - content: "\ea45"; + content: "\ea45"; } .ni-pin-3::before { - content: "\ea46"; + content: "\ea46"; } .ni-planet::before { - content: "\ea47"; + content: "\ea47"; } .ni-ruler-pencil::before { - content: "\ea48"; + content: "\ea48"; } .ni-satisfied::before { - content: "\ea49"; + content: "\ea49"; } .ni-scissors::before { - content: "\ea4a"; + content: "\ea4a"; } .ni-send::before { - content: "\ea4b"; + content: "\ea4b"; } .ni-settings-gear-65::before { - content: "\ea4c"; + content: "\ea4c"; } .ni-settings::before { - content: "\ea4d"; + content: "\ea4d"; } .ni-single-02::before { - content: "\ea4e"; + content: "\ea4e"; } .ni-single-copy-04::before { - content: "\ea4f"; + content: "\ea4f"; } .ni-sound-wave::before { - content: "\ea50"; + content: "\ea50"; } .ni-spaceship::before { - content: "\ea51"; + content: "\ea51"; } .ni-square-pin::before { - content: "\ea52"; + content: "\ea52"; } .ni-support-16::before { - content: "\ea53"; + content: "\ea53"; } .ni-tablet-button::before { - content: "\ea54"; + content: "\ea54"; } .ni-tag::before { - content: "\ea55"; + content: "\ea55"; } .ni-tie-bow::before { - content: "\ea56"; + content: "\ea56"; } .ni-time-alarm::before { - content: "\ea57"; + content: "\ea57"; } .ni-trophy::before { - content: "\ea58"; + content: "\ea58"; } .ni-tv-2::before { - content: "\ea59"; + content: "\ea59"; } .ni-umbrella-13::before { - content: "\ea5a"; + content: "\ea5a"; } .ni-user-run::before { - content: "\ea5b"; + content: "\ea5b"; } .ni-vector::before { - content: "\ea5c"; + content: "\ea5c"; } .ni-watch-time::before { - content: "\ea5d"; + content: "\ea5d"; } .ni-world::before { - content: "\ea5e"; + content: "\ea5e"; } .ni-zoom-split-in::before { - content: "\ea5f"; + content: "\ea5f"; } .ni-collection::before { - content: "\ea60"; + content: "\ea60"; } .ni-image::before { - content: "\ea61"; + content: "\ea61"; } .ni-shop::before { - content: "\ea62"; + content: "\ea62"; } .ni-ungroup::before { - content: "\ea63"; + content: "\ea63"; } .ni-world-2::before { - content: "\ea64"; + content: "\ea64"; } .ni-ui-04::before { - content: "\ea65"; + content: "\ea65"; } - /* all icon font classes list here */ diff --git a/florist/app/assets/css/nucleo-svg.css b/florist/app/assets/css/nucleo-svg.css index 4b397a75..17f91892 100644 --- a/florist/app/assets/css/nucleo-svg.css +++ b/florist/app/assets/css/nucleo-svg.css @@ -6,21 +6,21 @@ Icon colors -------------------------------- */ .icon { - display: inline-block; - /* icon primary color */ - color: #111111; - height: 1em; - width: 1em; + display: inline-block; + /* icon primary color */ + color: #111111; + height: 1em; + width: 1em; } .icon use { - /* icon secondary color - fill */ - fill: #7ea6f6; + /* icon secondary color - fill */ + fill: #7ea6f6; } .icon.icon-outline use { - /* icon secondary color - stroke */ - stroke: #7ea6f6; + /* icon secondary color - stroke */ + stroke: #7ea6f6; } /* -------------------------------- @@ -30,23 +30,23 @@ Change icon size -------------------------------- */ .icon-xs { - height: 0.5em; - width: 0.5em; + height: 0.5em; + width: 0.5em; } .icon-sm { - height: 0.8em; - width: 0.8em; + height: 0.8em; + width: 0.8em; } .icon-lg { - height: 1.6em; - width: 1.6em; + height: 1.6em; + width: 1.6em; } .icon-xl { - height: 2em; - width: 2em; + height: 2em; + width: 2em; } /* -------------------------------- @@ -56,23 +56,23 @@ Align icon and text -------------------------------- */ .icon-text-aligner { - /* add this class to parent element that contains icon + text */ - display: flex; - align-items: center; + /* add this class to parent element that contains icon + text */ + display: flex; + align-items: center; } .icon-text-aligner .icon { - color: inherit; - margin-right: 0.4em; + color: inherit; + margin-right: 0.4em; } .icon-text-aligner .icon use { - color: inherit; - fill: currentColor; + color: inherit; + fill: currentColor; } .icon-text-aligner .icon.icon-outline use { - stroke: currentColor; + stroke: currentColor; } /* -------------------------------- @@ -82,21 +82,21 @@ Icon reset values - used to enable color customizations -------------------------------- */ .icon { - fill: currentColor; - stroke: none; + fill: currentColor; + stroke: none; } .icon.icon-outline { - fill: none; - stroke: currentColor; + fill: none; + stroke: currentColor; } .icon use { - stroke: none; + stroke: none; } .icon.icon-outline use { - fill: none; + fill: none; } /* -------------------------------- @@ -110,26 +110,26 @@ Stroke effects - Nucleo outline icons -------------------------------- */ .icon-outline.icon-stroke-1 { - stroke-width: 1px; + stroke-width: 1px; } .icon-outline.icon-stroke-2 { - stroke-width: 2px; + stroke-width: 2px; } .icon-outline.icon-stroke-3 { - stroke-width: 3px; + stroke-width: 3px; } .icon-outline.icon-stroke-4 { - stroke-width: 4px; + stroke-width: 4px; } .icon-outline.icon-stroke-1 use, .icon-outline.icon-stroke-3 use { - -webkit-transform: translateX(0.5px) translateY(0.5px); - -moz-transform: translateX(0.5px) translateY(0.5px); - -ms-transform: translateX(0.5px) translateY(0.5px); - -o-transform: translateX(0.5px) translateY(0.5px); - transform: translateX(0.5px) translateY(0.5px); + -webkit-transform: translateX(0.5px) translateY(0.5px); + -moz-transform: translateX(0.5px) translateY(0.5px); + -ms-transform: translateX(0.5px) translateY(0.5px); + -o-transform: translateX(0.5px) translateY(0.5px); + transform: translateX(0.5px) translateY(0.5px); } diff --git a/florist/app/client_imports.tsx b/florist/app/client_imports.tsx index 18d2f931..7be41b03 100644 --- a/florist/app/client_imports.tsx +++ b/florist/app/client_imports.tsx @@ -1,4 +1,4 @@ -"use client" +"use client"; import { useEffect } from "react"; @@ -12,4 +12,6 @@ function ClientImports(): null { return null; } +const fetcher = (...args) => fetch(...args).then((res) => res.json()); +export { fetcher }; export default ClientImports; diff --git a/florist/app/jobs/hooks.tsx b/florist/app/jobs/hooks.tsx new file mode 100644 index 00000000..7a204b76 --- /dev/null +++ b/florist/app/jobs/hooks.tsx @@ -0,0 +1,10 @@ +import useSWR from "swr"; +import { fetcher } from "../client_imports"; + +export default function useGetJobsByJobStatus(status: string) { + const endpoint = "/api/server/job/".concat(status); + const { data, error, isLoading } = useSWR(endpoint, fetcher, { + refresh_interval: 1000, + }); + return { data, error, isLoading }; +} diff --git a/florist/app/jobs/page.tsx b/florist/app/jobs/page.tsx new file mode 100644 index 00000000..f0e6c311 --- /dev/null +++ b/florist/app/jobs/page.tsx @@ -0,0 +1,136 @@ +"use client"; +import { ReactElement } from "react/React"; +import useGetJobsByStatus from "./hooks"; +import useGetJobsByJobStatus from "./hooks"; + +export const validStatuses = { + NOT_STARTED: "Not Started", + IN_PROGRESS: "In Progress", + FINISHED_WITH_ERROR: "Finished with Error", + FINISHED_SUCCESSFULLY: "Finished Successfully", +}; + +interface JobData { + status: string; + model: string; + server_address: string; + server_info: string; + redis_host: string; + redis_port: string; + clients_info: Array; +} + +interface ClientInfo { + client: string; + service_address: string; + data_path: string; + redis_host: string; + redis_port: string; +} + +interface StatusProp { + status: string; +} + +export default function Page(): ReactElement { + const statusComponents = Object.keys(validStatuses).map((key, i) => ( + + )); + return ( +
+

Job Status

+ {statusComponents} +
+ ); +} + +export function Status({ status }: StatusProp): ReactElement { + const { data, error, isLoading } = useGetJobsByJobStatus(status); + if (error) return Help1; + if (isLoading) return Help2 ; + + return ( +
+

+ {validStatuses[status]} +

+ +
+ ); +} + +export function StatusTable({ + data, + status, +}: { + data: Array; + status: StatusProp; +}): ReactElement { + if (data.length > 0) { + return ( + + + + + + + + + +
ModelServer Address + Client Service Addresses{" "} +
+ ); + } else { + return ( +
+ + {" "} + No jobs to display.{" "} + +
+ ); + } +} + +export function TableRows({ data }: { data: Array }): ReactElement { + const tableRows = data.map((d, i) => ( + + )); + + return {tableRows}; +} + +export function TableRow({ + model, + serverAddress, + clientsInfo, +}: { + model: string; + serverAddress: string; + clientsInfo: Array; +}): ReactElement { + return ( + + {model} + {serverAddress} + + + ); +} + +export function ClientListTableData({ + clientsInfo, +}: { + clientsInfo: Array; +}): ReactElement { + const clientServiceAddressesString = clientsInfo + .map((c) => c.service_address) + .join(", "); + return {clientServiceAddressesString} ; +} diff --git a/florist/app/layout.tsx b/florist/app/layout.tsx index 5bbbd847..8efaedeb 100644 --- a/florist/app/layout.tsx +++ b/florist/app/layout.tsx @@ -8,28 +8,37 @@ import Script from "next/script"; import { useEffect } from "react"; import Sidebar from "./sidebar"; -import ClientImports from './client_imports'; +import ClientImports from "./client_imports"; export const metadata: Metadata = { - title: "Florist", + title: "Florist", }; export default function RootLayout({ children, }: { - children: React.ReactNode + children: React.ReactNode; }): ReactElement { return ( {/* Fonts and icons */} - + {/* Font Awesome Icons */}