├── server ├── app │ ├── __init__.py │ ├── utils.py │ ├── validation │ │ ├── constants.py │ │ ├── __init__.py │ │ ├── mqtt.py │ │ └── types.py │ ├── settings.py │ ├── errors.py │ ├── logs.py │ ├── database.py │ └── auth.py ├── migrations │ └── .gitkeep ├── tests │ ├── __init__.py │ ├── mosquitto.conf │ ├── README.md │ ├── test_mqtt.py │ ├── conftest.py │ └── data.json ├── .python-version ├── .gitignore ├── scripts │ ├── setup │ ├── initialize │ ├── check │ ├── build │ ├── README.md │ ├── initialize.py │ ├── jupyter │ ├── test │ └── develop ├── .env.example ├── Dockerfile ├── pyproject.toml └── schema.sql ├── dashboard ├── src │ ├── .gitignore │ ├── app │ │ ├── networks │ │ │ └── [networkIdentifier] │ │ │ │ ├── sensors │ │ │ │ └── [sensorIdentifier] │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── logs │ │ │ │ │ └── page.tsx │ │ │ │ │ ├── measurements │ │ │ │ │ └── page.tsx │ │ │ │ │ └── plots │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ ├── favicon.ico │ │ ├── swr-provider.tsx │ │ ├── offline │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── globals.css │ │ ├── login │ │ │ └── page.tsx │ │ ├── signup │ │ │ └── page.tsx │ │ └── style │ │ │ └── page.tsx │ ├── lib │ │ └── utils.ts │ ├── components │ │ ├── custom │ │ │ ├── auth-loading-screen.tsx │ │ │ ├── the-tenta.tsx │ │ │ ├── spinner.tsx │ │ │ ├── timestamp-label.tsx │ │ │ ├── navigation-bar.tsx │ │ │ ├── config-revision-tag.tsx │ │ │ ├── pagination.tsx │ │ │ └── creation-dialog.tsx │ │ └── ui │ │ │ ├── label.tsx │ │ │ ├── textarea.tsx │ │ │ ├── input.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── button.tsx │ │ │ ├── tabs.tsx │ │ │ ├── dialog.tsx │ │ │ └── select.tsx │ └── requests │ │ ├── status.ts │ │ ├── user.ts │ │ ├── measurements-aggregation.ts │ │ ├── networks.ts │ │ ├── sensors.ts │ │ └── configurations.ts ├── .eslintrc.json ├── public │ ├── tenta-favicon-1024.png │ ├── tenta-favicon-512.png │ ├── tenta-artwork-recolored.png │ ├── tenta-banner-dashboard-og-1024-512.png │ └── tenta-banner-dashboard-og-1200-630.png ├── postcss.config.js ├── next.config.js ├── README.md ├── .env.development ├── .env.template ├── components.json ├── tsconfig.json ├── Dockerfile ├── package.json ├── .gitignore └── tailwind.config.js ├── docs ├── public │ ├── banner.png │ └── architecture.png ├── netlify.toml ├── README.md ├── pages │ ├── _app.mdx │ ├── overview.mdx │ ├── introduction.mdx │ ├── roadmap.mdx │ ├── community.mdx │ ├── _meta.json │ ├── index.mdx │ ├── contribute.mdx │ ├── mqtt.mdx │ ├── deployment.mdx │ ├── connect.mdx │ ├── export.mdx │ └── design.mdx ├── next-env.d.ts ├── postcss.config.js ├── next.config.js ├── style.css ├── tailwind.config.js ├── tsconfig.json ├── package.json └── theme.config.jsx ├── publication ├── images │ ├── screenshot.png │ ├── architecture.png │ └── configurations.png └── paper.bib ├── .github └── workflows │ ├── openapi.yml │ ├── publication.yml │ └── test-server.yml ├── LICENSE ├── README.md └── docker-compose.yml /server/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dashboard/src/.gitignore: -------------------------------------------------------------------------------- 1 | !lib -------------------------------------------------------------------------------- /server/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | notebooks 2 | scripts/deploy 3 | -------------------------------------------------------------------------------- /dashboard/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /dashboard/src/app/networks/[networkIdentifier]/sensors/[sensorIdentifier]/.gitignore: -------------------------------------------------------------------------------- 1 | !logs/ -------------------------------------------------------------------------------- /docs/public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/docs/public/banner.png -------------------------------------------------------------------------------- /docs/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "/docs" 3 | publish = "/out" 4 | command = "npm run build" 5 | -------------------------------------------------------------------------------- /dashboard/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/dashboard/src/app/favicon.ico -------------------------------------------------------------------------------- /docs/public/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/docs/public/architecture.png -------------------------------------------------------------------------------- /publication/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/publication/images/screenshot.png -------------------------------------------------------------------------------- /publication/images/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/publication/images/architecture.png -------------------------------------------------------------------------------- /publication/images/configurations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/publication/images/configurations.png -------------------------------------------------------------------------------- /dashboard/public/tenta-favicon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/dashboard/public/tenta-favicon-1024.png -------------------------------------------------------------------------------- /dashboard/public/tenta-favicon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/dashboard/public/tenta-favicon-512.png -------------------------------------------------------------------------------- /dashboard/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /server/tests/mosquitto.conf: -------------------------------------------------------------------------------- 1 | listener 1883 2 | protocol mqtt 3 | persistence false 4 | log_dest stderr 5 | allow_anonymous true 6 | -------------------------------------------------------------------------------- /dashboard/public/tenta-artwork-recolored.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/dashboard/public/tenta-artwork-recolored.png -------------------------------------------------------------------------------- /dashboard/public/tenta-banner-dashboard-og-1024-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/dashboard/public/tenta-banner-dashboard-og-1024-512.png -------------------------------------------------------------------------------- /dashboard/public/tenta-banner-dashboard-og-1200-630.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iterize/tenta/HEAD/dashboard/public/tenta-banner-dashboard-og-1200-630.png -------------------------------------------------------------------------------- /dashboard/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { unoptimized: true } 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Run the documentation with 2 | 3 | ```bash 4 | npm install 5 | npm run dev 6 | ``` 7 | 8 | Create a production build with 9 | 10 | ```bash 11 | npm run build 12 | ``` 13 | -------------------------------------------------------------------------------- /dashboard/README.md: -------------------------------------------------------------------------------- 1 | # Tenta Dashboard 2 | 3 | Built using NextJS 13, Typscript, TailwindCSS and ShadcnUI. 4 | 5 | Run the development server with: 6 | 7 | ```bash 8 | npm run dev 9 | ``` 10 | -------------------------------------------------------------------------------- /dashboard/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /dashboard/src/app/swr-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SWRConfig } from "swr"; 4 | 5 | export const SWRProvider = (props: { children: React.ReactNode }) => { 6 | return {props.children}; 7 | }; 8 | -------------------------------------------------------------------------------- /docs/pages/_app.mdx: -------------------------------------------------------------------------------- 1 | import '../style.css'; 2 | 3 | export default function App({ Component, pageProps }) { 4 | return ( 5 | <> 6 | 7 | 8 | ); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /docs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /server/scripts/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Safety first 4 | set -o errexit -o pipefail -o nounset 5 | # Change into the project's directory 6 | cd "$(dirname "$0")/.." 7 | 8 | # Install the dependencies 9 | poetry install --with dev --sync --no-root 10 | -------------------------------------------------------------------------------- /server/scripts/initialize: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Safety first 4 | set -o errexit -o pipefail -o nounset 5 | # Change into the project's directory 6 | cd "$(dirname "$0")/.." 7 | 8 | # Initialize the database 9 | poetry run python -m scripts.initialize "$@" 10 | -------------------------------------------------------------------------------- /dashboard/src/components/custom/auth-loading-screen.tsx: -------------------------------------------------------------------------------- 1 | export function AuthLoadingScreen() { 2 | return ( 3 |
4 |
5 | loading the application 6 |
7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /dashboard/.env.development: -------------------------------------------------------------------------------- 1 | # required | URL of the tenta server (no trailing slash!) 2 | NEXT_PUBLIC_SERVER_URL="http://localhost:8421" 3 | 4 | # optional | rendered on the login page 5 | NEXT_PUBLIC_CONTACT_EMAIL="contact.email@login.page" 6 | 7 | # optional | rendered in the header 8 | NEXT_PUBLIC_INSTANCE_TITLE="Your Department Name" 9 | 10 | -------------------------------------------------------------------------------- /server/scripts/check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Safety first 4 | set -o errexit -o pipefail -o nounset 5 | # Change into the project's directory 6 | cd "$(dirname "$0")/.." 7 | 8 | poetry run black ./app ./tests ./scripts 9 | poetry run ruff --fix ./app ./tests ./scripts 10 | poetry run sqlfluff lint -v --disable-progress-bar ./app/queries.sql 11 | -------------------------------------------------------------------------------- /docs/postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | /** @type {import('postcss').Postcss} */ 4 | module.exports = { 5 | plugins: { 6 | "postcss-import": {}, 7 | "tailwindcss/nesting": {}, 8 | tailwindcss: {}, 9 | autoprefixer: {}, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /server/.env.example: -------------------------------------------------------------------------------- 1 | ENVIRONMENT=production 2 | 3 | # PostgreSQL credentials 4 | POSTGRESQL_HOSTNAME=www.example.com 5 | POSTGRESQL_PORT=5432 6 | POSTGRESQL_IDENTIFIER=username 7 | POSTGRESQL_PASSWORD=12345678 8 | POSTGRESQL_DATABASE=database 9 | 10 | # MQTT credentials 11 | MQTT_HOSTNAME=www.example.com 12 | MQTT_PORT=8883 13 | MQTT_IDENTIFIER=username 14 | MQTT_PASSWORD=12345678 15 | -------------------------------------------------------------------------------- /dashboard/.env.template: -------------------------------------------------------------------------------- 1 | # put this into the ".env.local" file 2 | 3 | # required | URL of the tenta server (no trailing slash!) 4 | NEXT_PUBLIC_SERVER_URL="http://your-server-domain.com" 5 | 6 | # optional | rendered on the login page 7 | NEXT_PUBLIC_CONTACT_EMAIL="contact.email@login.page" 8 | 9 | # optional | rendered in the header 10 | NEXT_PUBLIC_INSTANCE_TITLE="Your Department Name" 11 | 12 | -------------------------------------------------------------------------------- /docs/pages/overview.mdx: -------------------------------------------------------------------------------- 1 | # System overview 2 | 3 | Tenta consists of a server and a dashboard. The server communicates with the sensors via an intermediate MQTT broker and exposes a REST API for the dashboard. Data is stored in a PostgreSQL+TimescaleDB database. 4 | 5 | {/* TODO: Introduce demo instance here that can be used for all following examples */} 6 | 7 | ![Diagram of Tenta's architecture](/architecture.png) 8 | -------------------------------------------------------------------------------- /dashboard/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /docs/next.config.js: -------------------------------------------------------------------------------- 1 | const withNextra = require("nextra")({ 2 | theme: "nextra-theme-docs", 3 | themeConfig: "./theme.config.jsx", 4 | }); 5 | 6 | module.exports = withNextra({ 7 | images: { 8 | unoptimized: true, 9 | }, 10 | output: "export", 11 | }); 12 | 13 | // If you have other Next.js configurations, you can pass them as the parameter: 14 | // module.exports = withNextra({ /* other next.js config */ }) 15 | -------------------------------------------------------------------------------- /server/app/utils.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | 4 | 5 | def timestamp(): 6 | """Return current UTC time as unixtime float.""" 7 | return time.time() 8 | 9 | 10 | def backoff(): 11 | """Return exponential backoff intervals for retries.""" 12 | value = 1 13 | while True: 14 | yield value + random.random() - 0.5 15 | if value < 256: # Limit the backoff to about 5 minutes 16 | value *= 2 17 | -------------------------------------------------------------------------------- /dashboard/src/app/networks/[networkIdentifier]/sensors/[sensorIdentifier]/layout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export default function Page(props: { 4 | children: React.ReactNode; 5 | params: { networkIdentifier: string; sensorIdentifier: string }; 6 | }) { 7 | return ( 8 |
9 | {props.children} 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --nextra-navbar-height: 3.25rem; 7 | 8 | .nextra-banner-container { 9 | background: #f59e0b !important; 10 | } 11 | } 12 | 13 | .nx-duration-500 { 14 | transition-duration: 100ms; 15 | } 16 | 17 | .nx-transition-colors { 18 | transition-duration: 50ms; 19 | } 20 | 21 | footer div.nx-py-12 { 22 | @apply py-3 font-semibold text-sm text-gray-800 items-center justify-center; 23 | } 24 | -------------------------------------------------------------------------------- /server/scripts/build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Safety first 4 | set -o errexit -o pipefail -o nounset 5 | # Change into the project's directory 6 | cd "$(dirname "$0")/.." 7 | 8 | # Get the current commit hash and branch name 9 | COMMIT_SHA="$(git rev-parse --verify HEAD)" 10 | BRANCH_NAME="$(git branch --show-current)" 11 | 12 | # Build the docker image 13 | docker build --build-arg commit_sha="${COMMIT_SHA}" --build-arg branch_name="${BRANCH_NAME}" --tag tenta . 14 | yes | docker image prune 15 | -------------------------------------------------------------------------------- /server/app/validation/constants.py: -------------------------------------------------------------------------------- 1 | import enum 2 | 3 | 4 | class Limit(int, enum.Enum): 5 | SMALL = 2**6 # 64 6 | MEDIUM = 2**8 # 256 7 | LARGE = 2**14 # 16384 8 | MAXINT4 = 2**31 # Maximum value signed 32-bit integer + 1 9 | 10 | 11 | class Pattern(str, enum.Enum): 12 | NAME = r"^[a-z0-9](-?[a-z0-9])*$" 13 | KEY = r"^[a-z0-9](_?[a-z0-9])*$" 14 | IDENTIFIER = ( # Version 4 UUID regex 15 | r"^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" 16 | ) 17 | -------------------------------------------------------------------------------- /docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: "class", 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./theme.config.jsx", 7 | "./style.css", 8 | ], 9 | theme: { 10 | extend: { 11 | fontFamily: { 12 | serif: ["var(--next-font-google-crimson-pro)", "serif"], 13 | }, 14 | fontSize: { 15 | "2xs": ["0.625rem", "0.75rem"], 16 | }, 17 | }, 18 | }, 19 | plugins: [], 20 | darkMode: "class", 21 | }; 22 | -------------------------------------------------------------------------------- /dashboard/src/app/networks/[networkIdentifier]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { TheTenta } from "@/components/custom/the-tenta"; 4 | 5 | export default function Page(props: { params: { networkIdentifier: string } }) { 6 | return ( 7 |
8 |
9 | please select a sensor in the 10 | list 11 |
12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/openapi.yml: -------------------------------------------------------------------------------- 1 | name: openapi 2 | on: 3 | push: 4 | branches: [main] 5 | paths: 6 | - server/openapi.yml 7 | - .github/workflows/openapi.yml 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v3 14 | - name: Deploy OpenAPI documentation 15 | uses: bump-sh/github-action@v1 16 | with: 17 | file: server/openapi.yml 18 | doc: 24616c25-ad93-410b-8a2f-d3a9b96c04c6 19 | token: ${{ secrets.BUMP_TOKEN }} 20 | -------------------------------------------------------------------------------- /server/scripts/README.md: -------------------------------------------------------------------------------- 1 | # Development scripts 2 | 3 | - `build`: Build the Docker image 4 | - `check`: Format and lint the code 5 | - `develop`: Start a development instance with pre-populated example data 6 | - `initialize`: Initialize the database; Use `--populate` option to populate with example data 7 | - `jupyter`: Start a Jupyter server in the current environment 8 | - `setup`: Setup or update the dependencies after a `git clone` or `git pull` 9 | - `test`: Run the tests 10 | 11 | Styled after GitHub's ["Scripts to Rule Them All"](https://github.com/github/scripts-to-rule-them-all). 12 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": false, 7 | "forceConsistentCasingInFileNames": true, 8 | "noEmit": true, 9 | "incremental": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve" 16 | }, 17 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 18 | "exclude": ["node_modules"] 19 | } -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "repository": "https://github.com/iterize/tenta", 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build" 6 | }, 7 | "dependencies": { 8 | "autoprefixer": "^10.4.15", 9 | "next": "^13.4.19", 10 | "nextra": "^2.12.3", 11 | "nextra-theme-docs": "^2.12.3", 12 | "postcss": "^8.4.29", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "sharp": "^0.32.6", 16 | "tailwindcss": "^3.3.3" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^20.6.0", 20 | "typescript": "^5.2.2" 21 | }, 22 | "engines": { 23 | "node": ">=16" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/publication.yml: -------------------------------------------------------------------------------- 1 | name: Publication 2 | on: 3 | push: 4 | paths: 5 | - publication/** 6 | - .github/workflows/publication.yml 7 | 8 | jobs: 9 | paper: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v3 14 | - name: Build PDF 15 | uses: openjournals/openjournals-draft-action@master 16 | with: 17 | journal: joss 18 | paper-path: publication/paper.md 19 | - name: Upload artifact 20 | uses: actions/upload-artifact@v4 21 | with: 22 | name: paper 23 | path: publication/paper.pdf 24 | -------------------------------------------------------------------------------- /dashboard/src/components/custom/the-tenta.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | export function TheTenta(props: { className: string }) { 4 | return ( 5 |
6 | Tenta Artwork 13 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /dashboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /server/tests/README.md: -------------------------------------------------------------------------------- 1 | - The `data.json` file contains the example data that is loaded into the database during tests. It is also used to populate the database when you run the `./scripts/develop` script. Editing this file will break the tests but can be useful during development. 2 | - All timestamps in `data.json` are adjusted to the current time when they are written to the database. This ensures that the tests match the continuous aggregations and retention policies. A timestamp of `0` represents the current time rounded down to the nearest hour. 3 | - The tests expect available PostgreSQL+TimescaleDB and Mosquitto instances. This is consistent with the production environment. These services are automatically spun up locally inside the `./scripts/test` and `./scripts/develop` scripts. 4 | -------------------------------------------------------------------------------- /server/scripts/initialize.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | 4 | import tests.conftest 5 | 6 | 7 | async def initialize(populate=False): 8 | """Initialize the database schema, optionally populate with example data.""" 9 | async with tests.conftest._connection() as connection: 10 | with open("schema.sql") as file: 11 | for statement in file.read().split("\n\n\n"): 12 | await connection.execute(statement) 13 | if populate: 14 | await tests.conftest._populate(connection, tests.conftest._offset()) 15 | 16 | 17 | if __name__ == "__main__": 18 | parser = argparse.ArgumentParser() 19 | parser.add_argument("--populate", action="store_true") 20 | args = parser.parse_args() 21 | asyncio.run(initialize(populate=args.populate)) 22 | -------------------------------------------------------------------------------- /dashboard/src/components/custom/spinner.tsx: -------------------------------------------------------------------------------- 1 | export function Spinner() { 2 | return ( 3 | 15 | 16 | 20 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /dashboard/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 9 | ) 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )) 22 | Label.displayName = LabelPrimitive.Root.displayName 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /docs/pages/introduction.mdx: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This documentation aims to cover everything you need to know to use Tenta in your projects. 4 | 5 | If you get stuck somewhere or if you have any questions, don't hesitate to open a discussion on GitHub! If you find a bug, we're happy if you open an issue. For more insight into MQTT than we can provide here, the [HiveMQ MQTT Essentials guide](https://www.hivemq.com/mqtt-essentials/) is a great introduction. 6 | 7 | When you're new to a project, you have a very valuable view of the documentation: You can spot ambiguities and unclear explanations much better than maintainers! If you find an error somewhere or if you feel there's anything that can improve these docs, please don't hesitate to open an issue or a pull request on GitHub. 8 | 9 | All right, enough chit-chat, let's dive in! 🤿 10 | -------------------------------------------------------------------------------- /docs/pages/roadmap.mdx: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 |
4 | 5 | - [x] ~Live charts of measurements on the dashboard~ 6 | - [x] ~Optimize dashboard for mobile~ 7 | - [ ] Indicator if sensors are currently connected to the MQTT broker 8 | - [ ] Associate lon/lat/alt with each measurement and display this data in the dashboard 9 | - [ ] User management: add/remove users to/from a network, different roles 10 | - [ ] Demo instance to make Tenta easy to try out 11 | - [ ] Multi-tenancy: isolate data from different networks/sensors 12 | - [ ] Tagging system to record metadata (e.g., nearby construction work, changes to the hardware, etc.) 13 | - [ ] Compress data in the database to use less storage 14 | 15 | --- 16 | 17 | If you have a feature in mind that's not listed here, don't hesitate to open a [discussion on GitHub](https://github.com/iterize/tenta/discussions)! 🍰 18 | -------------------------------------------------------------------------------- /server/scripts/jupyter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Safety first 4 | set -o errexit -o pipefail -o nounset 5 | # Change into the project's directory 6 | cd "$(dirname "$0")/.." 7 | 8 | # Set our environment variables 9 | export ENVIRONMENT="development" 10 | export COMMIT_SHA=$(git rev-parse --verify HEAD) 11 | export BRANCH_NAME=$(git branch --show-current) 12 | export POSTGRESQL_HOSTNAME="localhost" 13 | export POSTGRESQL_PORT="5432" 14 | export POSTGRESQL_IDENTIFIER="postgres" 15 | export POSTGRESQL_PASSWORD="12345678" 16 | export POSTGRESQL_DATABASE="database" 17 | export MQTT_HOSTNAME="localhost" 18 | export MQTT_PORT="1883" 19 | export MQTT_IDENTIFIER="server" 20 | export MQTT_PASSWORD="password" 21 | 22 | # Enable importing local modules 23 | export PYTHONPATH=$(pwd) 24 | 25 | # Start jupyter server 26 | poetry run python -m jupyterlab --no-browser --port 8532 27 | -------------------------------------------------------------------------------- /dashboard/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |