From d63d8015bf539c53c02fc47ab212093a29b5792c Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 15:50:05 +0100 Subject: [PATCH 01/16] Remove reference to React Spectrum UI library --- docs/docs/index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/docs/index.md b/docs/docs/index.md index f067384f..d4803314 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -25,8 +25,6 @@ This documentation is about developing a Ground Station software using Telestion * [TypeScript](https://www.typescriptlang.org/) -- The programming language used for most components by Telestion * [Deno](https://deno.land/) -- The runtime used by Telestion for TypeScript and JavaScript based services * [React](https://reactjs.org/) -- The frontend framework used by Telestion -* [React Spectrum](https://react-spectrum.adobe.com/react-spectrum/index.html) -- The UI library used by Telestion - ## Discord From abc02b59d436e87ec3445aa69e895df04289a261 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 15:53:52 +0100 Subject: [PATCH 02/16] Update service description in service.md --- docs/docs/service.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/service.md b/docs/docs/service.md index ababc891..1ba43329 100644 --- a/docs/docs/service.md +++ b/docs/docs/service.md @@ -2,7 +2,7 @@ Services are small, self-contained, and (ideally) stateless applications that can be deployed and scaled independently. They're designed to be used in conjunction with other services and are often packaged together to form a larger application. -**In less abstract terms,** a service is a single application that is part of the Telestion application. It is a single application that is responsible for a single task. For example, the project template contains the following services by default: +**In less abstract terms,** a service is a single application that is part of the bigger Telestion application. It is a single application that is responsible for a single task. For example, the project template contains the following services by default: * Frontend: A web application that is responsible for displaying the user interface * Database Service: A service that is responsible for storing data From a7fd4013c271f4377a96d0a9c417b5ef46909ad8 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 15:58:26 +0100 Subject: [PATCH 03/16] Improve message bus documentation --- docs/docs/message-bus.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/docs/message-bus.md b/docs/docs/message-bus.md index a33c4e5b..3ac76239 100644 --- a/docs/docs/message-bus.md +++ b/docs/docs/message-bus.md @@ -1,10 +1,10 @@ # Message Bus -The message bus is a simple, lightweight, and fast way to send messages between different parts (_services_) of your application. It's a simple publish/subscribe system that allows you to send messages to a specific subject and have any listeners on that subject receive the message. +The message bus is a simple, lightweight, and fast way to send messages between different parts (_services_) of your application. It's a publish/subscribe system that allows you to send messages to a specific subject and have any listeners on that subject receive the message. ## Messages -Messages are the way that data is sent between services. In the Telestion ecosystem, messages are either JSON or binary data. +Messages are the way that data is sent between services. In the Telestion ecosystem. Messages are either [JSON](https://www.json.org/json-en.html) or binary data. ## Subjects @@ -70,11 +70,9 @@ sequenceDiagram ## NATS -The message bus is built on top of the [NATS](https://nats.io/) messaging system. This means that it is fast, lightweight, and reliable. - +We use [NATS](https://nats.io/) as our message bus. While all other [services](service.md) can be replaced, the message bus is a core component of Telestion. It is the backbone of the entire system. ### Authentication and Authorization -NATS also handles everything related to authentication and authorization for the message bus. This means that you can easily control who can send and receive messages on which subjects. - +NATS also handles everything related to authentication and authorization for the message bus. You can easily control who can send and receive messages on which subjects. From 4b1d0ce7440d4eefd8e13bdc2e82146b9f800266 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 16:09:01 +0100 Subject: [PATCH 04/16] Remove references to Rust in backend development documentation --- docs/docs/Backend Development/.pages | 1 - docs/docs/Backend Development/index.md | 2 -- .../Backend Development/other-languages.md | 18 +++--------------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/docs/docs/Backend Development/.pages b/docs/docs/Backend Development/.pages index 450b4ef5..02e9dae4 100644 --- a/docs/docs/Backend Development/.pages +++ b/docs/docs/Backend Development/.pages @@ -1,6 +1,5 @@ nav: - index.md - typescript - - rust - other-languages.md - Service Behavior Specification: service-behavior diff --git a/docs/docs/Backend Development/index.md b/docs/docs/Backend Development/index.md index 675812b2..77aae4c1 100644 --- a/docs/docs/Backend Development/index.md +++ b/docs/docs/Backend Development/index.md @@ -54,6 +54,4 @@ The most common way to write a backend service is to use TypeScript. However, for some use cases, it may be necessary to write a backend service in other languages. -Even some of the core services provided out of the box are written in Rust. To learn how to write a service in Rust, see [Using Rust](rust/index.md). - You can even write a backend service in any language you want. The only requirement is that it can communicate with the NATS message bus. To learn how to write a service in other languages, see [Using other languages](other-languages.md). diff --git a/docs/docs/Backend Development/other-languages.md b/docs/docs/Backend Development/other-languages.md index 3721d267..1e16a7d1 100644 --- a/docs/docs/Backend Development/other-languages.md +++ b/docs/docs/Backend Development/other-languages.md @@ -31,14 +31,12 @@ Every Telestion service receives at least the following environment variables: **If your service doesn't receive any of these environment variables, it should exit with a non-zero exit code.** !!! warning - There can be multiple instances of a service with the same name running at the same time. They are guaranteed to have the same configuration, apart from the `SERVICE_ID`. If you need a truly unique identifier, you can combine the `SERVICE_NAME` and the process ID. + There can be multiple instances of a service with the same name running at the same time. They are guaranteed to have the same configuration. If you need a truly unique identifier, you can combine the `SERVICE_NAME` and the process ID. ### Logging Your service should log any "feedback" to `stdout` and `stderr`. -For logging data to files, you should use the [Standard Operations Library](#standard-operations-library). - ### Queues NATS allows you to create queue groups. This means that you can have multiple instances of the same service running, and they'll share the messages they receive. @@ -63,20 +61,10 @@ While running, any Telestion service should respond (within 0.5 seconds) to requ ```json { "errors": 0, // or number of "recent" errors - "service": "my-service", // the SERVICE_ID "name": "My Service" // the SERVICE_NAME } ``` -## Standard Operations Library - -!!! info - The standard operations library is a library that provides a set of common operations that are used by many Telestion services. While it can be overwritten, every Telestion application comes with an implementation of these operations out-of-the-box that you can use. - -### Data Logging - -The standard operations library provides a simple way to log data to files. - -To use it, publish a message to the `__telestion__/log/[category]` subject with the body you want to log. `[category]` is the category of the log message. It can be any string. +## Service Behavior Specification -The standard operations library then logs the message to the file `logs/[category].log` in your data directory. +A formal description of the behavior of a Telestion service is provided in the [Service Behavior Specification](service-behavior/README.md). It can be used to test libraries for writing Telestion services in other languages. From 68f8b74632f6a25a07321fde6c01871cf18aeda0 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 16:16:28 +0100 Subject: [PATCH 05/16] Add note about samples in backend TypeScript docs --- docs/docs/Backend Development/typescript/.pages | 1 + docs/docs/Backend Development/typescript/index.md | 4 ++++ .../Backend Development/typescript/samples.md | 15 +++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 docs/docs/Backend Development/typescript/samples.md diff --git a/docs/docs/Backend Development/typescript/.pages b/docs/docs/Backend Development/typescript/.pages index fe43d7dc..d76b3221 100644 --- a/docs/docs/Backend Development/typescript/.pages +++ b/docs/docs/Backend Development/typescript/.pages @@ -4,4 +4,5 @@ nav: - configuration.md - message-bus.md - e2e-log-service.md + - samples.md - "API Reference ↗️": https://deno.land/x/telestion/mod.ts diff --git a/docs/docs/Backend Development/typescript/index.md b/docs/docs/Backend Development/typescript/index.md index 14438417..a0cd4ae9 100644 --- a/docs/docs/Backend Development/typescript/index.md +++ b/docs/docs/Backend Development/typescript/index.md @@ -86,3 +86,7 @@ deno run --allow-all service.ts --dev Now that you have a basic service running, you should have a look at how to make your service configurable. [Read more about configuration](configuration.md){ .md-button } + +If you prefer to learn by example, you can also have a look at the [samples](samples.md). + +[Browse samples on GitHub](https://github.com/wuespace/telestion/tree/main/backend-deno/samples){ .md-button } diff --git a/docs/docs/Backend Development/typescript/samples.md b/docs/docs/Backend Development/typescript/samples.md new file mode 100644 index 00000000..1eeb93b2 --- /dev/null +++ b/docs/docs/Backend Development/typescript/samples.md @@ -0,0 +1,15 @@ +# Samples + +You can find even more samples in the project's GitHub repository under `backend-deno/samples`: + +[Browse samples on GitHub](https://github.com/wuespace/telestion/tree/main/backend-deno/samples){ .md-button } + +## Running the samples + +You can run all the samples using the `docker-compose.yml` file in the linked folder. Just run the following command: + +```bash +docker-compose up +``` + +This will run all the samples, including the required NATS server. From 42222b03dc9d9b26b2f4460db39438961afb1a28 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 16:20:46 +0100 Subject: [PATCH 06/16] Refactor configuration documentation --- .../Backend Development/typescript/configuration.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/docs/docs/Backend Development/typescript/configuration.md b/docs/docs/Backend Development/typescript/configuration.md index 35344eca..c4406b8f 100644 --- a/docs/docs/Backend Development/typescript/configuration.md +++ b/docs/docs/Backend Development/typescript/configuration.md @@ -13,11 +13,6 @@ ## Configuration Sources -!!! warning - This section is specific to TypeScript services. Only the environment variables are standardized and must be implemented for all Telestion services. - - While services in other languages can use the same configuration sources, they aren't required to do so. - Services can be configured using the following sources: - Environment variables @@ -30,7 +25,6 @@ All these sources get combined into a single configuration object. If a configur 2. Environment variables 3. Configuration file - ### Environment Variables Environment variables are the most common way to configure a service. They are easy to use and supported by most platforms. @@ -82,8 +76,8 @@ Configuration sources and their order of precedence. Some configuration values are required for all services. These values are: * `NATS_URL`: The URL of the NATS server to connect to. -* `NATS_USER`: The username to use when connecting to NATS. -* `NATS_PASSWORD`: The password to use when connecting to NATS. +* `NATS_USER` (if the NATS server requires authentication): The username to use when connecting to NATS. +* `NATS_PASSWORD` (if the NATS user requires authentication): The password to use when connecting to NATS. * `SERVICE_NAME`: The name of the service. This is used to identify the service in the logs and in the NATS server. This is required for all services. * `DATA_DIR`: The directory where the service can store data. This is required for all services. @@ -101,7 +95,6 @@ Some configuration values are required for all services. These values are: This way, you don't have to set all the required configuration values when running the service locally. Without the `--dev` flag, the service fails if any of the required configuration values are missing. - ## Accessing the Configuration From 1414afa8f7b48597bed03eb6a302b08ec79b2b1d Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 16:32:19 +0100 Subject: [PATCH 07/16] Remove empty lines in concepts.md --- docs/docs/Deployment/concepts.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/docs/Deployment/concepts.md b/docs/docs/Deployment/concepts.md index 2938c3ec..2a71e453 100644 --- a/docs/docs/Deployment/concepts.md +++ b/docs/docs/Deployment/concepts.md @@ -34,7 +34,3 @@ A web server is server software, or hardware dedicated to running said software, Telestion uses NATS as a message broker. It's required for the communication between the Telestion services (both backend and frontend). NATS is a simple, secure and high-performance open source messaging system for cloud native applications, IoT messaging, and microservice architectures. - - - - From e96e17296e069bf54c2081ffae78291f563140c4 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 16:32:58 +0100 Subject: [PATCH 08/16] Remove extra blank lines in local-nats.md --- docs/docs/Deployment/local/local-nats.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/docs/Deployment/local/local-nats.md b/docs/docs/Deployment/local/local-nats.md index e07fe435..21beef0f 100644 --- a/docs/docs/Deployment/local/local-nats.md +++ b/docs/docs/Deployment/local/local-nats.md @@ -51,5 +51,3 @@ authorization { ``` This will create a user called `service` with the password `service`. This user will be able to publish to the `altitude` subject and subscribe to the `__telestion__.health` subject. - - From 17ac7e056396cf4bbb5b862c7b64100c0ffd0994 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 18:56:45 +0100 Subject: [PATCH 09/16] Update NATS description in Deployment documentation --- docs/docs/Deployment/index.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/docs/Deployment/index.md b/docs/docs/Deployment/index.md index 56ff0983..2486a2e5 100644 --- a/docs/docs/Deployment/index.md +++ b/docs/docs/Deployment/index.md @@ -4,7 +4,6 @@ tags: [ Deployment ] # Deployment - Deployment is the process of making a software system available for use. In the context of Telestion, deployment refers to the process of making the Telestion application available for use. !!! warning @@ -25,6 +24,6 @@ Telestion can be deployed in multiple ways: ## NATS and its configuration -Telestion uses [NATS](https://nats.io/) as a message broker. NATS is a lightweight, high-performance cloud native messaging system. It is used for the communication between the Telestion backend and the Telestion frontend. +Telestion uses [NATS](https://nats.io/) as its message broker. NATS is a lightweight, high-performance cloud-native messaging system. It is used for the communication between the Telestion backend and the Telestion frontend. No matter which deployment method you choose, you need to configure NATS. The configuration of NATS is described in the [NATS Configuration](nats/index.md) document. From d1734e7e7519150a1925147e243b826e9d845a72 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 19:15:28 +0100 Subject: [PATCH 10/16] Update NATS configuration for development environment --- docs/docs/Deployment/local/local-nats.md | 35 +++++++------------ docs/docs/Deployment/nats/index.md | 43 ++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 22 deletions(-) diff --git a/docs/docs/Deployment/local/local-nats.md b/docs/docs/Deployment/local/local-nats.md index 21beef0f..f7f0dac2 100644 --- a/docs/docs/Deployment/local/local-nats.md +++ b/docs/docs/Deployment/local/local-nats.md @@ -26,28 +26,19 @@ NATS can be configured using a configuration file. To run NATS with a configurat nats-server -c ``` -### Adding users - -!!! info - This guide will focus on getting you started. We'll look deeper into NATS permissions in the [NATS configuration guide](../nats/index.md). - -In your NATS configuration file, add the following section: - -```json -authorization { - default_permissions = { - publish = [] - subscribe = ["__telestion__.>"] - } - SERVICE = { - publish = ["altitude", "log.>"] - subscribe = ["altitude", "__telestion__.health"] - allow_responses = true - } - users = [ - {user: service, password: service, permissions: $SERVICE} - ] +As a starting point, you can use the following configuration to enable everything you need while developing: + +```json title="nats.conf" +http_port: 8222 + +websocket: { + port: 9222 + no_tls: true } ``` -This will create a user called `service` with the password `service`. This user will be able to publish to the `altitude` subject and subscribe to the `__telestion__.health` subject. +This will create a user called `nats` with the password `nats`. It will also enable the HTTP and WebSocket interfaces. + +Note that for production deployments, you need to configure NATS to use TLS and set up proper authentication. You can learn more about configuring NATS in the [NATS configuration guide](../nats/index.md). + +[Learn more about NATS configuration](../nats/index.md){ .md-button } diff --git a/docs/docs/Deployment/nats/index.md b/docs/docs/Deployment/nats/index.md index ca7ce774..23a0daf0 100644 --- a/docs/docs/Deployment/nats/index.md +++ b/docs/docs/Deployment/nats/index.md @@ -1,8 +1,51 @@ # NATS Server Configuration +The NATS server can be configured using both a configuration file and environment variables. + +## Environment Variables + The NATS server configuration is done via environment variables. The following table lists all available environment variables and their default values. | Environment Variable | Default Value | Description | |----------------------|---------------|------------------------------| | `NATS_HOST` | `localhost` | The host of the NATS server. | | `NATS_PORT` | `4222` | The port of the NATS server. | + +## Configuration File + +The NATS server can also be configured using a configuration file. To use a configuration file, you need to pass the `-c` flag to the NATS server: + +```bash +nats-server -c +``` + +You can find a full reference of the NATS server configuration in the [NATS documentation](https://docs.nats.io/nats-server/configuration). + +For Telestion, the following settings are of special interest: + +1. [`websocket`](https://docs.nats.io/running-a-nats-service/configuration/websocket/websocket_conf) - This section configures the WebSocket interface of the NATS server. It's used by the Telestion frontend to connect to the NATS server. +2. [`authorization`](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/authorization) - This section configures who can publish and subscribe to which subjects. +3. [`authorization.users`](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/username_password) - This section configures the user accounts that can connect to the NATS server. It's used to configure the user accounts that can connect to the NATS server. As of right now, Telestion exclusively supports [username/password-based authentication](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/auth_intro/username_password). + +### Development Configuration + +The following configuration is a good starting point for development. + +!!! danger + **Do not use this configuration in production!** It's only meant for development. + + There are several problems with this configuration that make it unsuitable for production: + + 1. it doesn't use TLS for the websocket interface, meaning that all communication is unencrypted + 2. it doesn't have any authentication or authorization configured, meaning that anyone can connect to the NATS server and publish/subscribe to any subject + + **In essence, if you were to use this configuration in production, you would have a NATS server that is publicly accessible and allows anyone with access to your server to publish/subscribe to any subject!** + +```json title="nats.conf" +http_port: 8222 + +websocket: { + port: 9222 + no_tls: true +} +``` From 85c8f27db1decf65572a5c7ca1238c583f4f4feb Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sat, 6 Jan 2024 23:51:22 +0100 Subject: [PATCH 11/16] Add Docker deployment guidelines --- docs/docs/Deployment/docker/index.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/docs/Deployment/docker/index.md b/docs/docs/Deployment/docker/index.md index 592ae19e..59db81f0 100644 --- a/docs/docs/Deployment/docker/index.md +++ b/docs/docs/Deployment/docker/index.md @@ -1,3 +1,19 @@ # Docker Deployment This document describes how to deploy Telestion using Docker. + +## Guidelines + +These guidelines are not strict rules, but they are recommended to follow. If you have a good reason to deviate from them, feel free to do so. Or in other words: If you don't know why you should deviate from them, don't do it. + +### Images per Service Type + +Depending on your project, it might make sense to have individual images for each service. However, for smaller projects, this is often both unnecessary and cumbersome. In this case, it is recommended to have one image for all services of a specific type. + +For example, you would have one image for all Deno based Backend services, one for the frontend, and so on. This way, you won't have to build and push huge numbers of images, and you can still use the same image for all services of a specific type. + +### Dependency Installation at Build Time + +If you have a service that requires dependencies to be installed, it is recommended to do so at build time. This way, you can be sure that the dependencies are installed when the image is built, and you don't have to wait for them to be installed when the container is started. + +This ensures both a faster startup time and a consistent execution environment. From ee38050bd7421eb6cc8813045f81d85f02bd322d Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sun, 7 Jan 2024 00:18:31 +0100 Subject: [PATCH 12/16] Add project folder structure documentation --- docs/docs/.pages | 2 +- docs/docs/project-folder-structure.md | 40 +++++++++++++++++++++++++++ docs/mkdocs.yml | 1 + 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 docs/docs/project-folder-structure.md diff --git a/docs/docs/.pages b/docs/docs/.pages index 2d3edba2..5fb72fef 100644 --- a/docs/docs/.pages +++ b/docs/docs/.pages @@ -2,10 +2,10 @@ collapse: true nav: - Overview: - index.md - - getting-started.md - Concepts: - service.md - message-bus.md + - project-folder-structure.md - Backend Development - Frontend Development - Deployment diff --git a/docs/docs/project-folder-structure.md b/docs/docs/project-folder-structure.md new file mode 100644 index 00000000..caae4dd7 --- /dev/null +++ b/docs/docs/project-folder-structure.md @@ -0,0 +1,40 @@ +# Project folder structure + +Every Telestion project is different, and so is its folder structure. Some projects might not even have a frontend and write every backend service in Java, while others might have a frontend and use Deno for their backend services. + +That's not very helpful, is it? So, let's take a look at a folder structure that is suitable for most projects. Note that as your project grows, you might want to change the structure to better suit your needs. But you will know when the time has come. + +## Version control + +The first thing you should do is to create a new Git repository. This repository will contain all the code for your project. You can use GitHub, GitLab, or any other Git hosting service you like. + +## Recommended folder structure + +The following folder structure is recommended for most projects: + +- :material-folder: `backend-deno` - A folder that contains all backend services written in Deno. + - :material-folder: `[service-name]` - A folder that contains a backend service written in Deno. + - :material-file: `mod.ts` - The entry point of the backend service. + - :material-file: `README.md` - A file that contains information about the backend service. + - :material-file: `Dockerfile` - A Dockerfile for the Deno-based backend services, if you want to use Docker. +- :material-folder: `frontend-react` - A folder that contains the frontend written in React. + - :material-file: `package.json` - The frontend application's `package.json` file. + - ... +- :material-folder: `frontend-cli` - A folder that contains the CLI frontend written in Deno. + - :material-file: `mod.ts` - The entry point of the CLI. + - :material-file: `README.md` - A file that contains information about the CLI. +- :material-folder: `nats` - A folder that contains the NATS server configuration. + - :material-file: `nats-server.conf` - The configuration file for the NATS server. +- :material-file: `docker-compose.yml` - A Docker Compose file that contains the configuration for the Docker containers. +- :material-file: `README.md` - A file that contains information about the project. + +## Alternatives + +There are also other options how you could structure your project. For example, if you have completely distinct groups of services that are not related to each other, you could create a folder for each group, and differentiate between programming languages used under these groups. + +However, to get started, the above structure should be sufficient. You can always change it later. + +*[backend service]: A service that acts on behalf of themself and does not require user interaction. +*[frontend]: A service that acts on behalf of the user and allows them to interact with the backend services. +*[CLI]: Command Line Interface +*[Deno]: A runtime for JavaScript and TypeScript diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 9d8017ea..9de81181 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -51,6 +51,7 @@ extra_javascript: - _js/mermaid-js/mermaid.min.js markdown_extensions: + - abbr - smarty - toc: permalink: true From 77ef9c02527e9e854a07e8b38daee96f9901687a Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sun, 7 Jan 2024 00:31:26 +0100 Subject: [PATCH 13/16] Add favicon to material theme --- docs/docs/_theme_img/favicon.png | Bin 0 -> 49580 bytes docs/mkdocs.yml | 1 + 2 files changed, 1 insertion(+) create mode 100644 docs/docs/_theme_img/favicon.png diff --git a/docs/docs/_theme_img/favicon.png b/docs/docs/_theme_img/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..bcf28acc4c6364cb4babd15f5eed5d471dd3ccb6 GIT binary patch literal 49580 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%0A6GRP*6JE$`eJL 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 Date: Sun, 7 Jan 2024 00:59:08 +0100 Subject: [PATCH 14/16] Improve documentation for Deno module --- backend-deno/mod.ts | 77 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/backend-deno/mod.ts b/backend-deno/mod.ts index 4fee978f..75669012 100644 --- a/backend-deno/mod.ts +++ b/backend-deno/mod.ts @@ -7,6 +7,31 @@ import {resolve} from "https://deno.land/std@0.183.0/path/mod.ts"; let args = Deno.args; let natsModule = nats; +/** + * The NATS `JSONCodec`, re-exported for convenience. + * + * See https://docs.nats.io/using-nats/developer/receiving/structure for details. + * + * @example Encoding data into a `Uint8Array` to publish it to NATS + * ```ts + * import {JSONCodec} from "https://deno.land/x/telestion/mod.ts"; + * + * const codec = JSONCodec(); + * + * const encoded = codec.encode({foo: "bar"}); + * console.log(encoded); // Uint8Array(13) + * ``` + * + * @example Decoding a `Uint8Array` received from NATS + * ```ts + * import {JSONCodec} from "https://deno.land/x/telestion/mod.ts"; + * + * const codec = JSONCodec(); + * + * const decoded = codec.decode(msg.data); + * console.log(decoded); // {foo: "bar"} + * ``` + */ export const JSONCodec = nats.JSONCodec; const StartServiceConfigSchema = z.object({ @@ -15,6 +40,21 @@ const StartServiceConfigSchema = z.object({ natsMock: z.unknown().optional(), }); +/** + * The minimal configuration for a Telestion service. Gets used internally by {@link startService}. + * + * See {@link MinimalConfig} for details about the resulting configuration object. + * + * @example Manually parsing the configuration + * ```ts + * import {MinimalConfigSchema} from "https://deno.land/x/telestion/mod.ts"; + * + * const rawConfig: unknown = Deno.env.toObject(); + * + * const config = MinimalConfigSchema.parse(rawConfig); + * console.log(config.SERVICE_NAME); // "my-service" + * ``` + */ export const MinimalConfigSchema = z.object({ NATS_URL: z.string(), NATS_USER: z.string().optional(), @@ -23,20 +63,45 @@ export const MinimalConfigSchema = z.object({ DATA_DIR: z.string(), }).passthrough(); +/** + * The minimal configuration for a Telestion service. Returned as `config` property by {@link startService}. + * + * ### Properties + * - `NATS_URL: string` The URL of the NATS server. + * - `NATS_USER?: string` The username for the NATS server. + * - `NATS_PASSWORD?: string` The password for the NATS server. + * - `SERVICE_NAME: string` The name of the service. + * - `DATA_DIR: string` The path to the data directory. + * + * Can also contain additional properties under `MinimalConfig[key: string]: unknown`. + */ export type MinimalConfig = z.infer; // Development mode => use default values +/** + * Options passed to the {@link startService} function. + * + * ### Properties + * - `nats: boolean = true` Whether to enable NATS or not. Disabling NATS can be useful during development. + * - `overwriteArgs?: string[]` An array of arguments that should overwrite the CLI arguments. Useful for testing. + * - `natsMock?: unknown` A mock for the NATS module. Useful for testing. + * + * @see {@link startService} + */ export type StartServiceConfig = z.infer; /** * Starts the service and returns the APIs available to the Telestion service. - * @param rawOptions The configuration for the service. - * @param rawOptions.nats Whether to enable NATS or not. Defaults to `true`. - * @param rawOptions.overwriteArgs An array of arguments that should overwrite the CLI arguments. Useful for testing. - * @param rawOptions.natsMock A mock for the NATS module. Useful for testing. - * - * @returns The APIs available to the Telestion service. + + * ### Service APIs returned by this function + * - `nc: NATSConnection` The NATS connection object. + * - `messageBus: MessageBus` The NATS message bus. Alias for `nc`. + * - `dataDir: string` The path to the data directory. + * - `serviceName: string` The name of the service. + * - `config: MinimalConfig` The configuration of the service. + + * @param rawOptions The configuration for the service. See {@link StartServiceConfig} for details. * * @throws If the service couldn't be started. * From 0644d07561a5a8e0bfef237b9d9039d4cd79cee7 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Sun, 7 Jan 2024 15:04:20 +0100 Subject: [PATCH 15/16] Add overview and frontend concepts --- docs/docs/Frontend Development/concepts.md | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/docs/Frontend Development/concepts.md b/docs/docs/Frontend Development/concepts.md index 460c2f22..62cfa1e7 100644 --- a/docs/docs/Frontend Development/concepts.md +++ b/docs/docs/Frontend Development/concepts.md @@ -7,10 +7,47 @@ This document describes the various concepts you need to know when building components for the Telestion frontend. +## Overview + +You can find the most important concepts of the Telestion frontend as well as how they relate to each other in the following diagram: + + ```mermaid + graph BT + frontend[Telestion Frontend] -->|is written in|react[React] + react -->|uses the programming language|ts[TypeScript] + user[User] -->|uses|frontend + subgraph frontend[Telestion Frontend] + dashboard[Dashboard] + widgetInstance[Widget Instances] + widget[Widgets] + end + dashboard[Dashboard] -->|is a layout of|widgetInstance[Widget Instances] + widget -->|defines the look and behavior of|widgetInstance[Widget Instances] + click widgetInstance "#widget-instances" "Widget Instance" + click widget "#widget" "Widget" + click dashboard "#dashboard" "Dashboard" + click react "#react" "React" + click ts "#typescript" "TypeScript" + ``` + +You can click on the elements in the diagram to learn more about them. + ## Frontend The Telestion frontend is the part of Telestion that is visible to the user. It is built with React and TypeScript. It is responsible for displaying data from the backend and for sending commands to the backend. +## Dashboard + +A dashboard is a layout of [widget instances](#widget-instances) with a specific configuration, created by the user. They are the main part of the Telestion frontend. + +## Widget Instances + +A widget instance is an instance of a widget with a specific configuration. They are the building blocks of [dashboards](#dashboard). + +## Widget + +A widget is the type of a widget instance. It contains the code that defines how the widget instance looks like and how it behaves. + ## HTML/CSS/JavaScript HTML, CSS, and JavaScript are the three core technologies of the web. HTML is used to describe the structure of web pages. CSS is used to describe the presentation of web pages. JavaScript is used to describe the behavior of web pages. From 0e4db70e444a77df64d09a3297a1541403575a4a Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Wed, 10 Jan 2024 15:55:22 +0100 Subject: [PATCH 16/16] Update local-nats.md --- docs/docs/Deployment/local/local-nats.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/Deployment/local/local-nats.md b/docs/docs/Deployment/local/local-nats.md index f7f0dac2..c05f1857 100644 --- a/docs/docs/Deployment/local/local-nats.md +++ b/docs/docs/Deployment/local/local-nats.md @@ -37,7 +37,7 @@ websocket: { } ``` -This will create a user called `nats` with the password `nats`. It will also enable the HTTP and WebSocket interfaces. +This will enable the HTTP and WebSocket interfaces. Note that for production deployments, you need to configure NATS to use TLS and set up proper authentication. You can learn more about configuring NATS in the [NATS configuration guide](../nats/index.md).