From bbc2faeb7b48c343f811317bcf25a2c04ef790ae Mon Sep 17 00:00:00 2001 From: jedi Date: Mon, 23 Oct 2023 15:37:20 +0200 Subject: [PATCH 1/8] update README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 854b54b..a2c0135 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ ``` bash git clone https://github.com/gr4yj3d1/toolshed.git ``` + or + ``` bash git clone https://git.neulandlabor.de/j3d1/toolshed.git ``` @@ -20,7 +22,9 @@ pip install -r requirements.txt python configure.py python manage.py runserver 0.0.0.0:8000 --insecure ``` -to run this in properly in production, you need to configure a webserver to serve the static files and proxy the requests to the backend, then run the backend with just `python manage.py runserver` without the `--insecure` flag. + +to run this in properly in production, you need to configure a webserver to serve the static files and proxy the +requests to the backend, then run the backend with just `python manage.py runserver` without the `--insecure` flag. ### Frontend @@ -37,8 +41,6 @@ cd toolshed/docs mkdocs serve ``` - - ## CLI Client ### Requirements From 6b5ccc2be6a2fd1048aa7268f809b19741bf44bc Mon Sep 17 00:00:00 2001 From: jedi Date: Mon, 23 Oct 2023 17:08:26 +0200 Subject: [PATCH 2/8] restructure docs --- README.md | 43 ++++++++++++++++-- docs/deployment.md | 102 ++++++++++++++++++++++++++++++++++++++++++ docs/development.md | 105 ++++++++++++++++++++++++++++++++++++++++++++ docs/federation.md | 23 ++++++++++ docs/index.md | 45 ++----------------- 5 files changed, 272 insertions(+), 46 deletions(-) create mode 100644 docs/deployment.md create mode 100644 docs/development.md create mode 100644 docs/federation.md diff --git a/README.md b/README.md index a2c0135..7f91e3e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # toolshed -## Installation / Development +## Development ``` bash git clone https://github.com/gr4yj3d1/toolshed.git @@ -12,7 +12,10 @@ or git clone https://git.neulandlabor.de/j3d1/toolshed.git ``` -### Backend +all following development mode commands support auto-reloading and hot-reloading where applicable, they do not need to bw +restarted after changes. + +### Backend only ``` bash cd toolshed/backend @@ -26,7 +29,7 @@ python manage.py runserver 0.0.0.0:8000 --insecure to run this in properly in production, you need to configure a webserver to serve the static files and proxy the requests to the backend, then run the backend with just `python manage.py runserver` without the `--insecure` flag. -### Frontend +### Frontend only ``` bash cd toolshed/frontend @@ -34,13 +37,45 @@ npm install npm run dev ``` -### Docs +### Docs only ``` bash cd toolshed/docs mkdocs serve ``` +### Full stack + +``` bash +cd toolshed +docker-compose -f deploy/docker-compose.override.yml up --build +``` + +## Deployment + +### Requirements + +- python3 +- python3-pip +- python3-venv +- wget +- unzip +- nginx +- uwsgi + +### Installation + +* Get the latest release from +`https://git.neulandlabor.de/j3d1/toolshed/releases/download//toolshed.zip` or +`https://github.com/gr4yj3d1/toolshed/archive/refs/tags/.zip`. +* Unpack it to `/var/www` or wherever you want to install toolshed. +* Create a virtual environment and install the requirements. +* Then run the configuration script. +* Configure your webserver to serve the static files and proxy the requests to the backend. +* Configure your webserver to run the backend with uwsgi. + +for detailed instructions see [docs](/docs/deployment.md). + ## CLI Client ### Requirements diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..9262ebe --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,102 @@ +# Deployment + +## Native + +### Requirements + +- python3 +- python3-pip +- python3-venv +- wget +- unzip +- nginx +- uwsgi +- certbot + +### Installation + +Get the latest release: + +``` bash +cd /var/www # or wherever you want to install toolshed +wget https://git.neulandlabor.de/j3d1/toolshed/releases/download//toolshed.zip +``` +or from github: +``` bash +cd /var/www # or wherever you want to install toolshed +wget https://github.com/gr4yj3d1/toolshed/archive/refs/tags/.zip -O toolshed.zip +``` + +Extract and configure the backend: + +``` bash +unzip toolshed.zip +cd toolshed/backend +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt +python configure.py +``` + +Configure uWSGI to serve the backend locally: + +``` bash +cd /var/www/toolshed/backend +cp toolshed.ini /etc/uwsgi/apps-available/ +ln -s /etc/uwsgi/apps-available/toolshed.ini /etc/uwsgi/apps-enabled/ +systemctl restart uwsgi +``` + +Configure nginx to serve the static files and proxy the requests to the backend: + +``` bash +cd /var/www/toolshed/backend +cp toolshed.nginx /etc/nginx/sites-available/toolshed +ln -s /etc/nginx/sites-available/toolsheed /etc/nginx/sites-enabled/ +systemctl restart nginx +``` + +Configure certbot to get a certificate for the domain: + +``` bash +certbot --nginx -d +``` + +### Update + +``` bash +cd /var/www +wget https://git.neulandlabor.de/j3d1/toolshed/releases/download//toolshed.zip +unzip toolshed.zip +cd toolshed/backend +source venv/bin/activate +pip install -r requirements.txt +python configure.py +systemctl restart uwsgi +``` + +## Docker + +### Requirements + +- docker +- docker-compose +- git + +### Installation + +``` bash +git clone https://git.neulandlabor.de/j3d1/toolshed.git +# or +git clone https://github.com/gr4yj3d1/toolshed.git +cd toolshed +docker-compose -f deploy/docker-compose.prod.yml up -d --build +``` + +### Update + +``` bash +toolshed +git pull +docker-compose -f deploy/docker-compose.prod.yml up -d --build +``` \ No newline at end of file diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..3fcd229 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,105 @@ +# Development + +``` bash +git clone https://github.com/gr4yj3d1/toolshed.git +``` + +or + +``` bash +git clone https://git.neulandlabor.de/j3d1/toolshed.git +``` + +## Native + +To a certain extent, the frontend and backend can be developed independently. The frontend is a Vue.js project and the +backend is a DRF (Django-Rest-Framework) project. If you want to develop the frontend, you can do so without the backend +and vice +versa. However, especially for the frontend, it is recommended to use the backend as well, as the frontend does not have +a lot of 'offline' functionality. +If you want to run the fullstack application, it is recommended to use the [docker-compose](#docker) method. + +### Frontend + +install `node.js` and `npm` + +on Debian* for example: `sudo apt install npm` + +``` bash +cd toolshed/frontend +npm install +npm run dev +``` + +### Backend + +Install `python3`, `pip` and `virtualenv` + +on Debian* for example: `sudo apt install python3 python3-pip python3-venv` + +Prepare backend environment + +``` bash +cd toolshed/backend +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +Run the test suite: + +``` bash +python manage.py test +``` + +optionally with coverage: + +``` bash +coverage run manage.py test +coverage report +``` + +Start the backend in development mode: + +``` bash +python manage.py migrate +cp .env.dist .env +echo "DEBUG = True" >> .env +python manage.py runserver 0.0.0.0:8000 +``` + +provides the api docs at `http://localhost:8000/docs/` + +### Docs (Wiki) + +Install `mkdocs` + +on Debian* for example: `sudo apt install mkdocs` + +Start the docs server: + +``` bash +cd toolshed/docs +mkdocs serve -a 0.0.0.0:8080 +``` + +## Docker + +### Fullstack + +Install `docker` and `docker-compose` + +on Debian* for example: `sudo apt install docker.io docker-compose` + +Start the fullstack application: + +``` bash +docker-compose -f deploy/docker-compose.override.yml up --build +``` + +This will start an instance of the frontend and wiki, a limited DoH (DNS over HTTPS) server and **two** instances of the backend. +The two backend instances are set up to use the domains `a.localhost` and `b.localhost`, the local DoH +server is used to direct the frontend to the correct backend instance. +The frontend is configured to act as if it was served from the domain `a.localhost`. +Access the frontend at `http://localhost:8080/`, backend at `http://localhost:8080/api/`, api docs +at `http://localhost:8080/docs/` and the wiki at `http://localhost:8080/wiki/`. \ No newline at end of file diff --git a/docs/federation.md b/docs/federation.md new file mode 100644 index 0000000..d242677 --- /dev/null +++ b/docs/federation.md @@ -0,0 +1,23 @@ +# Federation + +This section will cover how federation works in Toolshed. + +## What is Federation? + +Since user of Toolshed you can search and interact the inventory of all their 'friends' that are potentially on +different servers there is a need for a way to communicate between servers. We don't want to rely on a central server that +stores all the data and we don't want to have a central server that handles all the communication between servers. This +is where federation comes in. Toolshed uses a protocol that can not only exchange data with the server where the user +is registered but also with the servers where their friends are registered. + +## How does it work? + +Any user can register on any server and creates a personal key pair. The public key is stored on the server and the private +key is stored on the client. The private key is used to sign all requests to the server and the public key is used to +verify the signature. Once a user has registered on a server they can send friend requests to other users containing +their public key. If the other user accepts the friend request, the server stores the public key of the friend and +uses it to verify access to the friend's inventory. While accepting a friend request the user also automatically sends +their own public key to the friend's server. This way both users can access each other's inventory. + +The protocol is based on a simple HTTPS API exchanging JSON data that is signed with the user's private key. By default +Toolshed servers provide a documentation of the API at [/docs/api](/docs/api). \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 0ba4a97..95588dd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,47 +6,8 @@ This is the documentation for the Toolshed project. It is a work in progress. `#social` `#network` `#federation` `#decentralized` `#federated` `#socialnetwork` `#fediverse` `#community` `#hashtags` ## Getting Started + - [Deploying Toolshed](deployment.md) + - [Development Setup](development.md) + - [About Federation](federation.md) -## Installation -``` bash - # TODO add installation instructions - # similar to development instructions just with more docker - # TODO add docker-compose.yml -``` - -## Development - -``` bash -git clone https://github.com/gr4yj3d1/toolshed.git -``` -or -``` bash -git clone https://git.neulandlabor.de/j3d1/toolshed.git -``` - -### Frontend - -``` bash -cd toolshed/frontend -npm install -npm run dev -``` - -### Backend - -``` bash -cd toolshed/backend -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -python manage.py migrate -python manage.py runserver 0.0.0.0:8000 -``` - -### Docs - -``` bash -cd toolshed/docs -mkdocs serve -a 0.0.0.0:8080 -``` From 5217cdeeea0493a2abf25c4d8f6a04b200a45442 Mon Sep 17 00:00:00 2001 From: jedi Date: Thu, 22 Jun 2023 04:26:22 +0200 Subject: [PATCH 3/8] add datasets 'base' and 'screws' --- backend/shared_data/base.json | 59 +++++++++++++++++++++++++++++++++ backend/shared_data/screws.json | 30 +++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 backend/shared_data/base.json create mode 100644 backend/shared_data/screws.json diff --git a/backend/shared_data/base.json b/backend/shared_data/base.json new file mode 100644 index 0000000..d961bb8 --- /dev/null +++ b/backend/shared_data/base.json @@ -0,0 +1,59 @@ +{ + "categories": [ + { "name": "hardware"}, + { "name": "material"}, + { "name": "tools"} + ], + "properties": [ + { "name": "angle", "unit_symbol": "°", "unit_name": "degree", "unit_name_plural": "degrees" }, + { "name": "area", "unit_symbol": "m²", "unit_name": "square meter", "unit_name_plural": "square meters" }, + { "name": "current", "unit_symbol": "A", "unit_name": "ampere", "unit_name_plural": "amperes" }, + { "name": "diameter", "unit_symbol": "m", "unit_name": "meter", "unit_name_plural": "meters" }, + { "name": "energy", "unit_symbol": "J", "unit_name": "joule", "unit_name_plural": "joules" }, + { "name": "frequency", "unit_symbol": "Hz", "unit_name": "hertz", "unit_name_plural": "hertz" }, + { "name": "height", "unit_symbol": "m", "unit_name": "meter", "unit_name_plural": "meters" }, + { "name": "length", "unit_symbol": "m", "unit_name": "meter", "unit_name_plural": "meters" }, + { "name": "memory", "unit_symbol": "B", "unit_name": "byte", "unit_name_plural": "bytes", "base2_prefix": true }, + { "name": "power", "unit_symbol": "W", "unit_name": "watt", "unit_name_plural": "watts" }, + { "name": "price", "unit_symbol": "€", "unit_name": "euro", "unit_name_plural": "euros" }, + { "name": "speed", "unit_symbol": "m/s", "unit_name": "meter per second", "unit_name_plural": "meters per second" }, + { "name": "temperature", "unit_symbol": "°C", "unit_name": "degree Celsius", "unit_name_plural": "degrees Celsius" }, + { "name": "time", "unit_symbol": "s", "unit_name": "second", "unit_name_plural": "seconds" }, + { "name": "voltage", "unit_symbol": "V", "unit_name": "volt", "unit_name_plural": "volts" }, + { "name": "volume", "unit_symbol": "l", "unit_name": "liter", "unit_name_plural": "liters" }, + { "name": "weight", "unit_symbol": "g", "unit_name": "gram", "unit_name_plural": "grams" }, + { "name": "width", "unit_symbol": "m", "unit_name": "meter", "unit_name_plural": "meters" } + ], + "tags": [ + {"name": "bolt", "category": "hardware"}, + {"name": "chisel", "category": "tools"}, + {"name": "clamp", "category": "tools"}, + {"name": "drill", "category": "tools"}, + {"name": "ear plugs", "category": "tools"}, + {"name": "extension cord", "category": "tools"}, + {"name": "flashlight", "category": "tools"}, + {"name": "gloves", "category": "tools"}, + {"name": "goggles", "category": "tools"}, + {"name": "hammer", "category": "tools"}, + {"name": "level", "category": "tools"}, + {"name": "mask", "category": "tools"}, + {"name": "nail", "category": "hardware"}, + {"name": "nut", "category": "hardware"}, + {"name": "paint brush", "category": "tools"}, + {"name": "paint roller", "category": "tools"}, + {"name": "paint tray", "category": "tools"}, + {"name": "pliers", "category": "tools"}, + {"name": "power strip", "category": "tools"}, + {"name": "sander", "category": "tools"}, + {"name": "saw", "category": "tools"}, + {"name": "screw", "category": "hardware"}, + {"name": "screwdriver", "category": "tools"}, + {"name": "soldering iron", "category": "tools"}, + {"name": "stapler", "category": "tools"}, + {"name": "tape measure", "category": "tools"}, + {"name": "tool"}, + {"name": "vise", "category": "tools"}, + {"name": "washer", "category": "hardware"}, + {"name": "wrench", "category": "tools"} + ] +} \ No newline at end of file diff --git a/backend/shared_data/screws.json b/backend/shared_data/screws.json new file mode 100644 index 0000000..20c8b93 --- /dev/null +++ b/backend/shared_data/screws.json @@ -0,0 +1,30 @@ +{ + "depends": [ "git:base" ], + "categories": [ + { "name": "screws", "parent": "hardware"} + ], + "tags": [ + {"name": "m1", "category": "screws"}, + {"name": "m2", "category": "screws"}, + {"name": "m2.5", "category": "screws"}, + {"name": "m3", "category": "screws"}, + {"name": "m4", "category": "screws"}, + {"name": "m5", "category": "screws"}, + {"name": "m6", "category": "screws"}, + {"name": "m8", "category": "screws"}, + {"name": "m10", "category": "screws"}, + {"name": "m12", "category": "screws"}, + {"name": "m16", "category": "screws"}, + {"name": "torx", "category": "screws"}, + {"name": "hex", "category": "screws"}, + {"name": "phillips", "category": "screws"}, + {"name": "pozidriv", "category": "screws"}, + {"name": "slotted", "category": "screws"}, + {"name": "socket", "category": "screws"}, + {"name": "flat", "category": "screws"}, + {"name": "pan", "category": "screws"}, + {"name": "button", "category": "screws"}, + {"name": "countersunk", "category": "screws"}, + {"name": "round", "category": "screws"} + ] +} \ No newline at end of file From 2b6b889f826d69af9d9ab28b9debaf7277842151 Mon Sep 17 00:00:00 2001 From: jedi Date: Mon, 11 Mar 2024 14:21:31 +0100 Subject: [PATCH 4/8] add datasets 'ee', 'electrical', 'it' and 'tools' --- backend/shared_data/ee.json | 92 +++++++++++++++++++++++++++++ backend/shared_data/electrical.json | 62 +++++++++++++++++++ backend/shared_data/it.json | 82 +++++++++++++++++++++++++ backend/shared_data/tools.json | 68 +++++++++++++++++++++ 4 files changed, 304 insertions(+) create mode 100644 backend/shared_data/ee.json create mode 100644 backend/shared_data/electrical.json create mode 100644 backend/shared_data/it.json create mode 100644 backend/shared_data/tools.json diff --git a/backend/shared_data/ee.json b/backend/shared_data/ee.json new file mode 100644 index 0000000..3b0e73e --- /dev/null +++ b/backend/shared_data/ee.json @@ -0,0 +1,92 @@ +{ + "depends": [ "git:base" ], + "categories": [ + { "name": "electronics", "parent": "hardware"}, + { "name": "electronics", "parent": "tools"}, + { "name": "bus", "parent": "hardware/electronics"}, + { "name": "mcu", "parent": "hardware/electronics"}, + { "name": "wireless", "parent": "hardware/electronics"} + ], + "tags": [ + {"name": "smt", "category": "hardware/electronics"}, + {"name": "tht", "category": "hardware/electronics"}, + {"name": "adapter", "category": "hardware/electronics"}, + {"name": "amperemeter", "category": "tools/electronics"}, + {"name": "cable", "category": "hardware/electronics"}, + {"name": "camera", "category": "tools/electronics"}, + {"name": "connector", "category": "hardware/electronics"}, + {"name": "flux", "category": "hardware/electronics"}, + {"name": "microscope", "category": "tools/electronics"}, + {"name": "multimeter", "category": "tools/electronics"}, + {"name": "oscilloscope", "category": "tools/electronics"}, + {"name": "power supply", "category": "hardware/electronics"}, + {"name": "solder", "category": "hardware/electronics"}, + {"name": "soldering", "category": "tools/electronics"}, + {"name": "voltmeter", "category": "tools/electronics"}, + {"name": "charger", "category": "hardware/electronics"}, + {"name": "actuator", "category": "hardware/electronics"}, + {"name": "battery", "category": "hardware/electronics"}, + {"name": "capacitor", "category": "hardware/electronics"}, + {"name": "diode", "category": "hardware/electronics"}, + {"name": "display", "category": "hardware/electronics"}, + {"name": "encoder", "category": "hardware/electronics"}, + {"name": "fuse", "category": "hardware/electronics"}, + {"name": "inductor", "category": "hardware/electronics"}, + {"name": "inverter", "category": "hardware/electronics"}, + {"name": "lcd", "category": "hardware/electronics"}, + {"name": "led", "category": "hardware/electronics"}, + {"name": "motor", "category": "hardware/electronics"}, + {"name": "oscillator", "category": "hardware/electronics"}, + {"name": "potentiometer", "category": "hardware/electronics"}, + {"name": "relay", "category": "hardware/electronics"}, + {"name": "resistor", "category": "hardware/electronics"}, + {"name": "sensor", "category": "hardware/electronics"}, + {"name": "servo", "category": "hardware/electronics"}, + {"name": "stepper", "category": "hardware/electronics"}, + {"name": "switch", "category": "hardware/electronics"}, + {"name": "thermistor", "category": "hardware/electronics"}, + {"name": "thermocouple", "category": "hardware/electronics"}, + {"name": "transformer", "category": "hardware/electronics"}, + {"name": "transistor", "category": "hardware/electronics"}, + {"name": "bluetooth", "category": "hardware/electronics/wireless"}, + {"name": "gps", "category": "hardware/electronics/wireless"}, + {"name": "gsm", "category": "hardware/electronics/wireless"}, + {"name": "lora", "category": "hardware/electronics/wireless"}, + {"name": "nfc", "category": "hardware/electronics/wireless"}, + {"name": "rfid", "category": "hardware/electronics/wireless"}, + {"name": "thread", "category": "hardware/electronics/wireless"}, + {"name": "wifi", "category": "hardware/electronics/wireless"}, + {"name": "zigbee", "category": "hardware/electronics/wireless"}, + {"name": "zwave", "category": "hardware/electronics/wireless"}, + {"name": "can", "category": "hardware/electronics/bus"}, + {"name": "ethernet", "category": "hardware/electronics/bus"}, + {"name": "i2c", "category": "hardware/electronics/bus"}, + {"name": "lin", "category": "hardware/electronics/bus"}, + {"name": "spi", "category": "hardware/electronics/bus"}, + {"name": "uart", "category": "hardware/electronics/bus"}, + {"name": "usb", "category": "hardware/electronics/bus"}, + {"name": "arduino", "category": "hardware/electronics/mcu"}, + {"name": "atmega", "category": "hardware/electronics/mcu"}, + {"name": "attiny", "category": "hardware/electronics/mcu"}, + {"name": "beaglebone", "category": "hardware/electronics/mcu"}, + {"name": "esp32", "category": "hardware/electronics/mcu"}, + {"name": "esp8266", "category": "hardware/electronics/mcu"}, + {"name": "nucleo", "category": "hardware/electronics/mcu"}, + {"name": "raspberry", "category": "hardware/electronics/mcu"}, + {"name": "stm32", "category": "hardware/electronics/mcu"}, + {"name": "stm8", "category": "hardware/electronics/mcu"}, + {"name": "6502", "category": "hardware/electronics/mcu"}, + {"name": "8051", "category": "hardware/electronics/mcu"}, + {"name": "arm", "category": "hardware/electronics/mcu"}, + {"name": "avr", "category": "hardware/electronics/mcu"}, + {"name": "cortex", "category": "hardware/electronics/mcu"}, + {"name": "m68k", "category": "hardware/electronics/mcu"}, + {"name": "mips", "category": "hardware/electronics/mcu"}, + {"name": "pic", "category": "hardware/electronics/mcu"}, + {"name": "powerpc", "category": "hardware/electronics/mcu"}, + {"name": "risc-v", "category": "hardware/electronics/mcu"}, + {"name": "x86", "category": "hardware/electronics/mcu"}, + {"name": "xtensa", "category": "hardware/electronics/mcu"}, + {"name": "z80", "category": "hardware/electronics/mcu"} + ] +} \ No newline at end of file diff --git a/backend/shared_data/electrical.json b/backend/shared_data/electrical.json new file mode 100644 index 0000000..c0a6fac --- /dev/null +++ b/backend/shared_data/electrical.json @@ -0,0 +1,62 @@ +{ + "depends": ["git:base"], + "categories": [ + { "name": "electrical"}, + { "name": "connectors"}, + { "name": "power", "parent": "connectors" } + ], + "tags": [ + { "name": "braker", "category": "electrical" }, + { "name": "cable", "category": "electrical" }, + { "name": "connector", "category": "electrical" }, + { "name": "plug", "category": "connectors" }, + { "name": "socket", "category": "connectors" }, + { "name": "power", "category": "connectors" }, + { "name": "C1", "category": "connectors/power" }, + { "name": "C2", "category": "connectors/power" }, + { "name": "C3", "category": "connectors/power" }, + { "name": "C4", "category": "connectors/power" }, + { "name": "C5", "category": "connectors/power" }, + { "name": "C6", "category": "connectors/power" }, + { "name": "C7", "category": "connectors/power" }, + { "name": "C7P", "category": "connectors/power" }, + { "name": "C8", "category": "connectors/power" }, + { "name": "C8P", "category": "connectors/power" }, + { "name": "C9", "category": "connectors/power" }, + { "name": "C10", "category": "connectors/power" }, + { "name": "C11", "category": "connectors/power" }, + { "name": "C12", "category": "connectors/power" }, + { "name": "C13", "category": "connectors/power" }, + { "name": "C14", "category": "connectors/power" }, + { "name": "C15", "category": "connectors/power" }, + { "name": "C15A", "category": "connectors/power" }, + { "name": "C16", "category": "connectors/power" }, + { "name": "C16A", "category": "connectors/power" }, + { "name": "C17", "category": "connectors/power" }, + { "name": "C18", "category": "connectors/power" }, + { "name": "C19", "category": "connectors/power" }, + { "name": "C20", "category": "connectors/power" }, + { "name": "C21", "category": "connectors/power" }, + { "name": "C22", "category": "connectors/power" }, + { "name": "C23", "category": "connectors/power" }, + { "name": "C24", "category": "connectors/power" }, + { "name": "Type A", "category": "connectors/power", "description": "NEMA 1-15, U.S. 2 pin" }, + { "name": "Type B", "category": "connectors/power", "description": "NEMA 5-15, U.S. 3 pin" }, + { "name": "Type C", "category": "connectors/power", "description": "CEE 7/16, Europlug" }, + { "name": "Type D", "category": "connectors/power", "description": "BS 546, India 5A/15A" }, + { "name": "Type E", "category": "connectors/power", "description": "CEE 7/5, French 2 pin" }, + { "name": "Type F", "category": "connectors/power", "description": "CEE 7/4, Schuko" }, + { "name": "Type E/F", "category": "connectors/power", "description": "CEE 7/7, Schuko/French hybrid" }, + { "name": "Type G", "category": "connectors/power", "description": "BS 1363, U.K. 3 pin" }, + { "name": "Type H", "category": "connectors/power", "description": "SI 32 Israel"}, + { "name": "Type I", "category": "connectors/power", "description": "AS/NZS 3112, Australia 3 pin" }, + { "name": "Type J", "category": "connectors/power", "description": "SEV 1011, Swiss 3 pin" }, + { "name": "Type K", "category": "connectors/power", "description": "DS 60884-2-D1, Danish 3 pin" }, + { "name": "Type L", "category": "connectors/power", "description": "CEI 23-16/VII, Italian 3 pin" } + ], + "properties": [ + { "name": "max current", "description": "Current rating"}, + { "name": "max power", "description": "Power rating" }, + { "name": "grid frequency", "description": "Typical frequency" } + ] +} \ No newline at end of file diff --git a/backend/shared_data/it.json b/backend/shared_data/it.json new file mode 100644 index 0000000..56f6189 --- /dev/null +++ b/backend/shared_data/it.json @@ -0,0 +1,82 @@ +{ + "depends": [ "git:electrical" ], + "categories": [ + { "name": "pc", "description": "PC related" }, + { "name": "usb", "parent": "connectors" } + ], + "tags": [ + { "name": "case", "category": "pc" }, + { "name": "cooler", "category": "pc" }, + { "name": "cpu" }, + { "name": "drone" }, + { "name": "fan", "category": "pc" }, + { "name": "gpu" }, + { "name": "hdd" }, + { "name": "headset" }, + { "name": "hub" }, + { "name": "iot" }, + { "name": "keyboard" }, + { "name": "laptop" }, + { "name": "memory" }, + { "name": "microphone" }, + { "name": "monitor" }, + { "name": "motherboard" }, + { "name": "mouse" }, + { "name": "pc"}, + { "name": "power supply" }, + { "name": "printer" }, + { "name": "router" }, + { "name": "scanner" }, + { "name": "server" }, + { "name": "smartphone" }, + { "name": "smartwatch" }, + { "name": "speaker" }, + { "name": "ssd" }, + { "name": "switch" }, + { "name": "tablet" }, + { "name": "watercooling" , "category": "pc"}, + { "name": "webcam" }, + { "name": "workstation" }, + { "name": "USB", "category": "connectors" }, + { "name": "Type A", "category": "connectors/usb" }, + { "name": "Type B", "category": "connectors/usb" }, + { "name": "Type C", "category": "connectors/usb" }, + { "name": "Micro", "category": "connectors/usb" }, + { "name": "Mini", "category": "connectors/usb" }, + { "name": "USB 2.0", "category": "connectors/usb" }, + { "name": "USB 3.0", "category": "connectors/usb" }, + { "name": "USB 3.1", "category": "connectors/usb" }, + { "name": "USB 3.2", "category": "connectors/usb" }, + { "name": "OTG", "category": "connectors/usb" }, + { "name": "thunderbolt", "category": "connectors/usb" }, + { "name": "24pin", "category": "connectors" }, + { "name": "8pin", "category": "connectors" }, + { "name": "atx", "category": "connectors" }, + { "name": "chinch", "category": "connectors" }, + { "name": "displayport", "category": "connectors" }, + { "name": "dvi", "category": "connectors" }, + { "name": "eps", "category": "connectors" }, + { "name": "floppy", "category": "connectors" }, + { "name": "hdmi", "category": "connectors" }, + { "name": "ide", "category": "connectors" }, + { "name": "jack", "category": "connectors" }, + { "name": "m.2", "category": "connectors" }, + { "name": "molex", "category": "connectors" }, + { "name": "p4", "category": "connectors" }, + { "name": "pcie", "category": "connectors" }, + { "name": "qsfp", "category": "connectors" }, + { "name": "qsfp+", "category": "connectors" }, + { "name": "qsfp28", "category": "connectors" }, + { "name": "rj11", "category": "connectors" }, + { "name": "rj45", "category": "connectors" }, + { "name": "sas", "category": "connectors" }, + { "name": "sata", "category": "connectors" }, + { "name": "scsi", "category": "connectors" }, + { "name": "sfp", "category": "connectors" }, + { "name": "sfp+", "category": "connectors" }, + { "name": "sfp28", "category": "connectors" }, + { "name": "toslink", "category": "connectors" }, + { "name": "vga", "category": "connectors" }, + { "name": "xlr", "category": "connectors" } + ] +} \ No newline at end of file diff --git a/backend/shared_data/tools.json b/backend/shared_data/tools.json new file mode 100644 index 0000000..d4c4f88 --- /dev/null +++ b/backend/shared_data/tools.json @@ -0,0 +1,68 @@ +{ + "depends": ["git:base"], + "categories": [ + { "name": "powertools", "parent": "tools" } + ], + "tags": [ + { "name": "3d printer"}, + { "name": "air compressor", "category": "powertools"}, + { "name": "air filter"}, + { "name": "automotive", "category": "tools" }, + { "name": "bandsaw"}, + { "name": "belt sander"}, + { "name": "bench grinder"}, + { "name": "circular saw", "category": "powertools"}, + { "name": "concrete", "category": "tools" }, + { "name": "construction", "category": "tools" }, + { "name": "corded", "category": "powertools" }, + { "name": "cordless", "category": "powertools" }, + { "name": "disc sander"}, + { "name": "drill press"}, + { "name": "drywall", "category": "tools" }, + { "name": "dust collector"}, + { "name": "dust mask"}, + { "name": "ear protection"}, + { "name": "electrical", "category": "tools" }, + { "name": "eye protection"}, + { "name": "fire extinguisher"}, + { "name": "first aid"}, + { "name": "generator", "category": "powertools"}, + { "name": "glue gun", "category": "powertools"}, + { "name": "grinder", "category": "powertools"}, + { "name": "hammer drill", "category": "powertools"}, + { "name": "handheld", "category": "tools" }, + { "name": "heat gun", "category": "powertools"}, + { "name": "impact driver", "category": "powertools"}, + { "name": "jigsaw"}, + { "name": "jointer"}, + { "name": "ladder"}, + { "name": "laser cutter"}, + { "name": "lathe"}, + { "name": "masonry", "category": "tools" }, + { "name": "mechanical", "category": "tools" }, + { "name": "metalworking", "category": "tools" }, + { "name": "mill"}, + { "name": "multitool"}, + { "name": "nailgun", "category": "powertools"}, + { "name": "oscillating tool", "category": "powertools"}, + { "name": "painting", "category": "tools" }, + { "name": "planer"}, + { "name": "plasma cutter"}, + { "name": "plumbing", "category": "tools" }, + { "name": "powerplane", "category": "powertools"}, + { "name": "pressure washer", "category": "powertools"}, + { "name": "roofing", "category": "tools" }, + { "name": "router table"}, + { "name": "router", "category": "powertools"}, + { "name": "sawhorse"}, + { "name": "sawzall", "category": "powertools"}, + { "name": "screwgun", "category": "powertools"}, + { "name": "stationary", "category": "tools" }, + { "name": "tablesaw"}, + { "name": "tile", "category": "tools" }, + { "name": "welder", "category": "powertools"}, + { "name": "woodworking", "category": "tools" }, + { "name": "workbench"}, + { "name": "worklight"} + ] +} From af5cfedcab444ce4ccc457e339d4dcd8ba77c236 Mon Sep 17 00:00:00 2001 From: jedi Date: Mon, 11 Mar 2024 15:25:04 +0100 Subject: [PATCH 5/8] add datasets 'ee_packages' --- backend/shared_data/ee_packages.json | 99 ++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 backend/shared_data/ee_packages.json diff --git a/backend/shared_data/ee_packages.json b/backend/shared_data/ee_packages.json new file mode 100644 index 0000000..5701202 --- /dev/null +++ b/backend/shared_data/ee_packages.json @@ -0,0 +1,99 @@ +{ + "depends": [ "git:ee" ], + "categories": [ + { "name": "packages", "parent": "hardware/electronics", "description": "Component Packages"} + ], + "tags": [ + {"name": "0201", "category": "hardware/electronics/packages"}, + {"name": "0402", "category": "hardware/electronics/packages"}, + {"name": "0603", "category": "hardware/electronics/packages"}, + {"name": "0805", "category": "hardware/electronics/packages"}, + {"name": "1206", "category": "hardware/electronics/packages"}, + {"name": "1210", "category": "hardware/electronics/packages"}, + {"name": "1812", "category": "hardware/electronics/packages"}, + {"name": "MELF", "category": "hardware/electronics/packages"}, + {"name": "MiniMELF", "category": "hardware/electronics/packages"}, + {"name": "MicroMELF", "category": "hardware/electronics/packages"}, + {"name": "SMC", "category": "hardware/electronics/packages"}, + {"name": "SMB", "category": "hardware/electronics/packages"}, + {"name": "SMA", "category": "hardware/electronics/packages"}, + {"name": "GF1", "category": "hardware/electronics/packages"}, + {"name": "DIP-x", "category": "hardware/electronics/packages", + "description": "Dual Inline Package, needs pin count property to be unambiguous"}, + {"name": "SOD", "category": "hardware/electronics/packages"}, + {"name": "SOD-123", "category": "hardware/electronics/packages"}, + {"name": "SOD-323", "category": "hardware/electronics/packages"}, + {"name": "SOD-523", "category": "hardware/electronics/packages"}, + {"name": "SOD-923", "category": "hardware/electronics/packages"}, + {"name": "SOT", "category": "hardware/electronics/packages"}, + {"name": "SOT23", "category": "hardware/electronics/packages"}, + {"name": "SOT23-3", "category": "hardware/electronics/packages"}, + {"name": "SOT323", "category": "hardware/electronics/packages"}, + {"name": "SOT416", "category": "hardware/electronics/packages"}, + {"name": "SOT23-5", "category": "hardware/electronics/packages"}, + {"name": "SOT353", "category": "hardware/electronics/packages"}, + {"name": "SOT553", "category": "hardware/electronics/packages"}, + {"name": "SOT23-6", "category": "hardware/electronics/packages"}, + {"name": "SOT363", "category": "hardware/electronics/packages"}, + {"name": "SOT563", "category": "hardware/electronics/packages"}, + {"name": "SOT23-8", "category": "hardware/electronics/packages"}, + {"name": "SOT54", "category": "hardware/electronics/packages", "alias":"TO-92"}, + {"name": "SOT143", "category": "hardware/electronics/packages"}, + {"name": "SOT343", "category": "hardware/electronics/packages"}, + {"name": "SOT490", "category": "hardware/electronics/packages"}, + {"name": "SOT89-3", "category": "hardware/electronics/packages"}, + {"name": "SOT89-5", "category": "hardware/electronics/packages"}, + {"name": "SOT223-4", "category": "hardware/electronics/packages"}, + {"name": "SOT223-5", "category": "hardware/electronics/packages"}, + {"name": "SOT223-8", "category": "hardware/electronics/packages"}, + {"name": "TO-3", "category": "hardware/electronics/packages"}, + {"name": "TO-5", "category": "hardware/electronics/packages"}, + {"name": "TO-8", "category": "hardware/electronics/packages"}, + {"name": "TO-18", "category": "hardware/electronics/packages"}, + {"name": "TO-39", "category": "hardware/electronics/packages"}, + {"name": "TO-66", "category": "hardware/electronics/packages"}, + {"name": "TO-92", "category": "hardware/electronics/packages"}, + {"name": "TO-220", "category": "hardware/electronics/packages"}, + {"name": "TO-247", "category": "hardware/electronics/packages"}, + {"name": "TO-251", "category": "hardware/electronics/packages"}, + {"name": "TO-252", "category": "hardware/electronics/packages"}, + {"name": "TO-263", "category": "hardware/electronics/packages"}, + {"name": "TO-264", "category": "hardware/electronics/packages"}, + {"name": "TO-268", "category": "hardware/electronics/packages"}, + {"name": "TO-269", "category": "hardware/electronics/packages"}, + {"name": "SOIC-x", "category": "hardware/electronics/packages", "alias": "SO-x", + "description": "Small Outline Integrated Circuit, needs pin count property to be unambiguous"}, + {"name": "SOJ-x", "category": "hardware/electronics/packages", + "description": "Small Outline J-leaded, needs pin count property to be unambiguous"}, + {"name": "MSOP-x", "category": "hardware/electronics/packages", + "description": "Mini Small Outline Package, needs pin count property to be unambiguous"}, + {"name": "SSOP-x", "category": "hardware/electronics/packages", + "description": "Shrink Small Outline Package, needs pin count property to be unambiguous"}, + {"name": "SOP-x", "category": "hardware/electronics/packages", + "description": "Small Outline Package, needs pin count property to be unambiguous"}, + {"name": "TSOP-x", "category": "hardware/electronics/packages", + "description": "Thin Small Outline Package, needs pin count property to be unambiguous"}, + {"name": "TSSOP-x", "category": "hardware/electronics/packages", + "description": "Thin Shrink Small Outline Package, needs pin count property to be unambiguous"}, + {"name": "QFP-x", "category": "hardware/electronics/packages", + "description": "Quad Flat Package, needs pin count property to be unambiguous"}, + {"name": "TQFP-x", "category": "hardware/electronics/packages", + "description": "Thin Quad Flat Package, needs pin count property to be unambiguous"}, + {"name": "LQFP-x", "category": "hardware/electronics/packages", + "description": "Low-profile Quad Flat Package, needs pin count property to be unambiguous"}, + {"name": "DFN-x", "category": "hardware/electronics/packages", + "description": "Dual Flat No-leaded, needs pin count property to be unambiguous"}, + {"name": "QFN-x", "category": "hardware/electronics/packages", + "description": "Quad Flat No-leaded, needs pin count property to be unambiguous"}, + {"name": "TQFN-x", "category": "hardware/electronics/packages", + "description": "Thin Quad Flat No-leaded, needs pin count property to be unambiguous"}, + {"name": "LQFN-x", "category": "hardware/electronics/packages", + "description": "Low-profile Quad Flat No-leaded, needs pin count property to be unambiguous"}, + {"name": "UQFN-x", "category": "hardware/electronics/packages", + "description": "Ultra-thin Quad Flat No-leaded, needs pin count property to be unambiguous"} + ], + "properties": [ + { "name": "pin count", "unit_symbol": "", "unit_name": "", "unit_name_plural": "" } + ], + "url": "https://en.wikipedia.org/wiki/List_of_integrated_circuit_packaging_types" +} \ No newline at end of file From 0fd49bc023ed247a0a7424f50f141672f717cfad Mon Sep 17 00:00:00 2001 From: jedi Date: Mon, 8 Apr 2024 20:54:29 +0200 Subject: [PATCH 6/8] add javascript dns resolver using DoH --- frontend/src/dns.js | 50 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 frontend/src/dns.js diff --git a/frontend/src/dns.js b/frontend/src/dns.js new file mode 100644 index 0000000..f07ea22 --- /dev/null +++ b/frontend/src/dns.js @@ -0,0 +1,50 @@ +import {query} from 'dns-query'; + +function get_prefered_server() { + try { + const servers = JSON.parse(localStorage.getItem('dns-servers')); + if (servers && servers.length > 0) { + return servers; + } + } catch (e) { + console.error(e); + } + const request = new XMLHttpRequest(); + request.open('GET', '/local/dns', false); + request.send(null); + if (request.status === 200) { + const servers = JSON.parse(request.responseText); + if (servers && servers.length > 0) { + return servers; + } + } + return ['1.1.1.1', '8.8.8.8']; +} + +class FallBackResolver { + constructor() { + this._servers = get_prefered_server(); + this._cache = JSON.parse(localStorage.getItem('dns-cache')) || {}; + } + + async query(domain, type) { + const key = domain + ':' + type; + if (key in this._cache && this._cache[key].time > Date.now() - 1000 * 60 * 60) { + const age_seconds = Math.ceil(Date.now() / 1000 - this._cache[key].time / 1000); + return [this._cache[key].data]; + } + const result = await query( + {question: {type: type, name: domain}}, + { + endpoints: this._servers, + } + ) + if (result.answers.length === 0) throw new Error('No answer'); + const first = result.answers[0]; + this._cache[key] = {time: Date.now(), ...first}; // TODO hadle multiple answers + localStorage.setItem('dns-cache', JSON.stringify(this._cache)); + return [first.data]; + } +} + +export default FallBackResolver; \ No newline at end of file From 8d64a3c5287f3a696f56ba97ab8ac14c96973a76 Mon Sep 17 00:00:00 2001 From: jedi Date: Mon, 8 Apr 2024 20:57:04 +0200 Subject: [PATCH 7/8] add vuex store and federation layer for api calls --- frontend/src/federation.js | 324 +++++++++++++++++++++++++++++++++++++ frontend/src/main.js | 3 +- frontend/src/neigbors.js | 48 ++++++ frontend/src/store.js | 132 +++++++++++++++ 4 files changed, 506 insertions(+), 1 deletion(-) create mode 100644 frontend/src/federation.js create mode 100644 frontend/src/neigbors.js create mode 100644 frontend/src/store.js diff --git a/frontend/src/federation.js b/frontend/src/federation.js new file mode 100644 index 0000000..9afe33c --- /dev/null +++ b/frontend/src/federation.js @@ -0,0 +1,324 @@ +class ServerSet { + constructor(servers, unreachable_neighbors) { + if (!servers || !Array.isArray(servers)) { + throw new Error('no servers') + } + if (!unreachable_neighbors || typeof unreachable_neighbors.queryUnreachable !== 'function' || typeof unreachable_neighbors.unreachable !== 'function') { + throw new Error('no unreachable_neighbors') + } + this.servers = [...new Set(servers)] // deduplicate + this.unreachable_neighbors = unreachable_neighbors; + } + + add(server) { + console.log('adding server', server) + if (!server || typeof server !== 'string') { + throw new Error('server must be a string') + } + if (server in this.servers) { + console.log('server already in set', server) + return + } + this.servers.push(server); + } + + async get(auth, target) { + if (!auth || typeof auth.buildAuthHeader !== 'function') { + throw new Error('no auth') + } + for (const server of this.servers) { + try { + if (this.unreachable_neighbors.queryUnreachable(server)) { + continue + } + const url = "https://" + server + target // TODO https + return await fetch(url, { + method: 'GET', + headers: { + ...auth.buildAuthHeader(url) + }, + credentials: 'omit' + }).catch(err => { + console.error('get from server failed', server, err) + this.unreachable_neighbors.unreachable(server) + } + ).then(response => response.json()) + } catch (e) { + console.error('get from server failed', server, e) + } + } + throw new Error('all servers failed') + } + + async post(auth, target, data) { + if (!auth || typeof auth.buildAuthHeader !== 'function') { + throw new Error('no auth') + } + for (const server of this.servers) { + try { + if (this.unreachable_neighbors.queryUnreachable(server)) { + continue + } + const url = "https://" + server + target // TODO https + return await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...auth.buildAuthHeader(url, data) + }, + credentials: 'omit', + body: JSON.stringify(data) + }).catch(err => { + console.error('post to server failed', server, err) + this.unreachable_neighbors.unreachable(server) + } + ).then(response => response.json()) + } catch (e) { + console.error('post to server failed', server, e) + } + } + throw new Error('all servers failed') + } + + async patch(auth, target, data) { + if (!auth || typeof auth.buildAuthHeader !== 'function') { + throw new Error('no auth') + } + for (const server of this.servers) { + try { + if (this.unreachable_neighbors.queryUnreachable(server)) { + continue + } + const url = "https://" + server + target // TODO https + return await fetch(url, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + ...auth.buildAuthHeader(url, data) + }, + credentials: 'omit', + body: JSON.stringify(data) + }).catch(err => { + console.error('patch to server failed', server, err) + this.unreachable_neighbors.unreachable(server) + } + ).then(response => response.json()) + } catch (e) { + console.error('patch to server failed', server, e) + } + } + throw new Error('all servers failed') + } + + async put(auth, target, data) { + if (!auth || typeof auth.buildAuthHeader !== 'function') { + throw new Error('no auth') + } + for (const server of this.servers) { + try { + if (this.unreachable_neighbors.queryUnreachable(server)) { + continue + } + const url = "https://" + server + target // TODO https + return await fetch(url, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + ...auth.buildAuthHeader(url, data) + }, + credentials: 'omit', + body: JSON.stringify(data) + }).catch(err => { + console.error('put to server failed', server, err) + this.unreachable_neighbors.unreachable(server) + } + ).then(response => response.json()) + } catch (e) { + console.error('put to server failed', server, e) + } + } + throw new Error('all servers failed') + } + + async delete(auth, target) { + if (!auth || typeof auth.buildAuthHeader !== 'function') { + throw new Error('no auth') + } + for (const server of this.servers) { + try { + if (this.unreachable_neighbors.queryUnreachable(server)) { + continue + } + const url = "https://" + server + target // TODO https + return await fetch(url, { + method: 'DELETE', + headers: { + ...auth.buildAuthHeader(url) + }, + credentials: 'omit' + }).catch(err => { + console.error('delete from server failed', server, err) + this.unreachable_neighbors.unreachable(server) + } + ) + } catch (e) { + console.error('delete from server failed', server, e) + } + } + throw new Error('all servers failed') + } + + async getRaw(auth, target) { + if (!auth || typeof auth.buildAuthHeader !== 'function') { + throw new Error('no auth') + } + for (const server of this.servers) { + try { + if (this.unreachable_neighbors.queryUnreachable(server)) { + continue + } + const url = "https://" + server + target // TODO https + return await fetch(url, { + method: 'GET', + headers: { + ...auth.buildAuthHeader(url) + }, + credentials: 'omit' + }).catch(err => { + console.error('get from server failed', server, err) + this.unreachable_neighbors.unreachable(server) + } + ) + } catch (e) { + console.error('get from server failed', server, e) + } + } + throw new Error('all servers failed') + } +} + +class ServerSetUnion { + constructor(serverSets) { + if (!serverSets || !Array.isArray(serverSets)) { + throw new Error('no serverSets') + } + this.serverSets = serverSets; + } + + add(serverset) { + if (!serverset || !(serverset instanceof ServerSet)) { + throw new Error('no serverset') + } + if (this.serverSets.find(s => serverset.servers.every(s2 => s.servers.includes(s2)))) { + console.warn('serverset already in union', serverset) + return + } + this.serverSets.push(serverset) + } + + async get(auth, target) { + try { + return await this.serverSets.reduce(async (acc, serverset) => { + return acc.then(async (acc) => { + return acc.concat(await serverset.get(auth, target)) + }) + }, Promise.resolve([])) + } catch (e) { + throw new Error('all servers failed') + } + } + + async post(auth, target, data) { + try { + return await this.serverSets.reduce(async (acc, serverset) => { + return acc.then(async (acc) => { + return acc.concat(await serverset.post(auth, target, data)) + }) + }, Promise.resolve([])) + } catch (e) { + throw new Error('all servers failed') + } + } + + async patch(auth, target, data) { + try { + return await this.serverSets.reduce(async (acc, serverset) => { + return acc.then(async (acc) => { + return acc.concat(await serverset.patch(auth, target, data)) + }) + }, Promise.resolve([])) + } catch (e) { + throw new Error('all servers failed') + } + } + + async put(auth, target, data) { + try { + return await this.serverSets.reduce(async (acc, serverset) => { + return acc.then(async (acc) => { + return acc.concat(await serverset.put(auth, target, data)) + }) + }, Promise.resolve([])) + } catch (e) { + throw new Error('all servers failed') + } + } + + async delete(auth, target) { + try { + return await this.serverSets.reduce(async (acc, serverset) => { + return acc.then(async (acc) => { + return acc.concat(await serverset.delete(auth, target)) + }) + }, Promise.resolve([])) + } catch (e) { + throw new Error('all servers failed') + } + } +} + + +class authMethod { + constructor(method, auth) { + this.method = method; + this.auth = auth; + } + + buildAuthHeader(url, data) { + return this.method(this.auth, {url, data}) + } + +} + +function createSignAuth(username, signKey) { + const context = {username, signKey} + if (!context.signKey || !context.username || typeof context.username !== 'string' + || !(context.signKey instanceof Uint8Array) || context.signKey.length !== 64) { + throw new Error('no signKey or username') + } + return new authMethod(({signKey, username}, {url, data}) => { + const json = JSON.stringify(data) + const signature = nacl.crypto_sign_detached(nacl.encode_utf8(url + (data ? json : "")), signKey) + return {'Authorization': 'Signature ' + username + ':' + nacl.to_hex(signature)} + }, context) +} + +function createTokenAuth(token) { + const context = {token} + if (!context.token) { + throw new Error('no token') + } + return new authMethod(({token}, {url, data}) => { + return {'Authorization': 'Token ' + token} + }, context) +} + +function createNullAuth() { + return new authMethod(() => { + return {} + }, {}) +} + +export {ServerSet, ServerSetUnion, createSignAuth, createTokenAuth, createNullAuth}; + + diff --git a/frontend/src/main.js b/frontend/src/main.js index 66ab5c7..2277ac2 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -5,10 +5,11 @@ import App from './App.vue' import './scss/toolshed.scss' import router from './router' +import store from './store'; import _nacl from 'js-nacl'; -const app = createApp(App).use(BootstrapIconsPlugin); +const app = createApp(App).use(store).use(BootstrapIconsPlugin); _nacl.instantiate((nacl) => { window.nacl = nacl diff --git a/frontend/src/neigbors.js b/frontend/src/neigbors.js new file mode 100644 index 0000000..bbdfc06 --- /dev/null +++ b/frontend/src/neigbors.js @@ -0,0 +1,48 @@ +class NeighborsCache { + constructor() { + //this._max_age = 1000 * 60 * 60; // 1 hour + //this._max_age = 1000 * 60 * 5; // 5 minutes + this._max_age = 1000 * 15; // 15 seconds + this._cache = JSON.parse(localStorage.getItem('neighbor-cache')) || {}; + } + + reachable(domain) { + console.log('reachable neighbor ' + domain) + if (domain in this._cache) { + delete this._cache[domain]; + localStorage.setItem('neighbor-cache', JSON.stringify(this._cache)); + } + } + + unreachable(domain) { + console.log('unreachable neighbor ' + domain) + this._cache[domain] = {time: Date.now()}; + localStorage.setItem('neighbor-cache', JSON.stringify(this._cache)); + } + + queryUnreachable(domain) { + //return false if unreachable + if (domain in this._cache) { + if (this._cache[domain].time > Date.now() - this._max_age) { + console.log('skip unreachable neighbor ' + domain + ' ' + Math.ceil( + Date.now()/1000 - this._cache[domain].time/1000) + 's/' + Math.ceil(this._max_age/1000) + 's') + return true + } else { + delete this._cache[domain]; + localStorage.setItem('neighbor-cache', JSON.stringify(this._cache)); + } + } + return false; + } + + list() { + return Object.entries(this._cache).map(([domain, elem]) => { + return { + domain: domain, + time: elem.time + } + }) + } +} + +export default NeighborsCache; \ No newline at end of file diff --git a/frontend/src/store.js b/frontend/src/store.js new file mode 100644 index 0000000..6d0dd73 --- /dev/null +++ b/frontend/src/store.js @@ -0,0 +1,132 @@ +import {createStore} from 'vuex'; +import router from '@/router'; +import FallBackResolver from "@/dns"; +import NeighborsCache from "@/neigbors"; +import {createNullAuth, createSignAuth, createTokenAuth, ServerSet, ServerSetUnion} from "@/federation"; + + +export default createStore({ + state: { + local_loaded: false, + last_load: {}, + user: null, + token: null, + keypair: null, + remember: false, + home_servers: null, + resolver: new FallBackResolver(), + unreachable_neighbors: new NeighborsCache(), + }, + mutations: { + setUser(state, user) { + state.user = user; + if (state.remember) + localStorage.setItem('user', user); + }, + setToken(state, token) { + state.token = token; + if (state.remember) + localStorage.setItem('token', token); + }, + setKey(state, keypair) { + state.keypair = nacl.crypto_sign_keypair_from_seed(nacl.from_hex(keypair)) + if (state.remember) + localStorage.setItem('keypair', nacl.to_hex(state.keypair.signSk).slice(0, 64)) + }, + setRemember(state, remember) { + state.remember = remember; + if (!remember) { + localStorage.removeItem('user'); + localStorage.removeItem('token'); + localStorage.removeItem('keypair'); + } + localStorage.setItem('remember', remember); + }, + setHomeServers(state, home_servers) { + state.home_servers = home_servers; + }, + logout(state) { + state.user = null; + state.token = null; + state.keypair = null; + localStorage.removeItem('user'); + localStorage.removeItem('token'); + localStorage.removeItem('keypair'); + router.push('/login'); + }, + load_local(state) { + if (state.local_loaded) + return; + const remember = localStorage.getItem('remember'); + const user = localStorage.getItem('user'); + const token = localStorage.getItem('token'); + const keypair = localStorage.getItem('keypair'); + if (user && token) { + this.commit('setUser', user); + this.commit('setToken', token); + if (keypair) { + this.commit('setKey', keypair) + } + } + state.cache_loaded = true; + } + }, + actions: { + async login({commit, dispatch, state, getters}, {username, password, remember}) { + commit('setRemember', remember); + const data = await dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors)) + .then(set => set.post(getters.nullAuth, '/auth/token/', {username, password})) + if (data.token && data.key) { + commit('setToken', data.token); + commit('setUser', username); + commit('setKey', data.key); + const s = await dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors)) + commit('setHomeServers', s) + return true; + } else { + return false; + } + }, + async lookupServer({state}, {username}) { + const domain = username.split('@')[1] + const request = '_toolshed-server._tcp.' + domain + '.' + return await state.resolver.query(request, 'SRV').then( + (result) => result.map( + (answer) => answer.target + ':' + answer.port)) + }, + async getHomeServers({state, dispatch, commit}) { + if (state.home_servers) + return state.home_servers + const promise = dispatch('lookupServer', {username: state.user}).then(servers => new ServerSet(servers, state.unreachable_neighbors)) + commit('setHomeServers', promise) + return promise + }, + async getFriendServers({state, dispatch, commit}, {username}) { + return dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors)) + }, + }, + getters: { + isLoggedIn(state) { + if (!state.local_loaded) { + state.remember = localStorage.getItem('remember') === 'true' + state.user = localStorage.getItem('user') + state.token = localStorage.getItem('token') + const keypair = localStorage.getItem('keypair') + if (keypair) + state.keypair = nacl.crypto_sign_keypair_from_seed(nacl.from_hex(keypair)) + state.local_loaded = true + } + + return state.user !== null && state.token !== null; + }, + signAuth(state) { + return createSignAuth(state.user, state.keypair.signSk) + }, + tokenAuth(state) { + return createTokenAuth(state.token) + }, + nullAuth(state) { + return createNullAuth({}) + }, + } +}) \ No newline at end of file From 9acf5a97e2c9b265a26e2255ed574f293a27b336 Mon Sep 17 00:00:00 2001 From: jedi Date: Sat, 12 Oct 2024 16:02:41 +0200 Subject: [PATCH 8/8] add explicit non-interactive option to configure.py --- backend/configure.py | 59 +++++++++++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/backend/configure.py b/backend/configure.py index ee26cac..e027805 100755 --- a/backend/configure.py +++ b/backend/configure.py @@ -8,32 +8,41 @@ import dotenv from django.db import transaction, IntegrityError -def yesno(prompt, default=False): - if not sys.stdin.isatty(): - return default - yes = {'yes', 'y', 'ye'} - no = {'no', 'n'} +class CmdCtx: - if default: - yes.add('') - else: - no.add('') + def __init__(self, args): + self.args = args - hint = ' [Y/n] ' if default else ' [y/N] ' - - while True: - choice = input(prompt + hint).lower() - if choice in yes: + def yesno(self, prompt, default=False): + if not sys.stdin.isatty() or self.args.noninteractive: + return default + elif self.args.yes: return True - elif choice in no: + elif self.args.no: return False + yes = {'yes', 'y', 'ye'} + no = {'no', 'n'} + + if default: + yes.add('') else: - print('Please respond with "yes" or "no"') + no.add('') + + hint = ' [Y/n] ' if default else ' [y/N] ' + + while True: + choice = input(prompt + hint).lower() + if choice in yes: + return True + elif choice in no: + return False + else: + print('Please respond with "yes" or "no"') -def configure(): +def configure(ctx): if not os.path.exists('.env'): - if not yesno("the .env file does not exist, do you want to create it?", default=True): + if not ctx.yesno("the .env file does not exist, do you want to create it?", default=True): print('Aborting') exit(0) if not os.path.exists('.env.dist'): @@ -56,7 +65,7 @@ def configure(): current_hosts = os.getenv('ALLOWED_HOSTS') print('Current ALLOWED_HOSTS: {}'.format(current_hosts)) - if yesno("Do you want to add ALLOWED_HOSTS?"): + if ctx.yesno("Do you want to add ALLOWED_HOSTS?"): hosts = input("Enter a comma-separated list of allowed hosts: ") joined_hosts = current_hosts + ',' + hosts if current_hosts else hosts dotenv.set_key('.env', 'ALLOWED_HOSTS', joined_hosts) @@ -67,20 +76,21 @@ def configure(): django.setup() if not os.path.exists('db.sqlite3'): - if not yesno("No database found, do you want to create one?", default=True): + if not ctx.yesno("No database found, do you want to create one?", default=True): print('Aborting') exit(0) from django.core.management import call_command call_command('migrate') - if yesno("Do you want to create a superuser?"): + if ctx.yesno("Do you want to create a superuser?"): from django.core.management import call_command call_command('createsuperuser') call_command('collectstatic', '--no-input') - if yesno("Do you want to import all categories, properties and tags contained in this repository?", default=True): + if ctx.yesno("Do you want to import all categories, properties and tags contained in this repository?", + default=True): from hostadmin.serializers import CategorySerializer, PropertySerializer, TagSerializer from hostadmin.models import ImportedIdentifierSets from hashlib import sha256 @@ -196,6 +206,7 @@ def main(): parser = ArgumentParser(description='Toolshed Server Configuration') parser.add_argument('--yes', '-y', help='Answer yes to all questions', action='store_true') parser.add_argument('--no', '-n', help='Answer no to all questions', action='store_true') + parser.add_argument('--noninteractive', '-x', help="Run in noninteractive mode", action='store_true') parser.add_argument('cmd', help='Command', default='configure', nargs='?') args = parser.parse_args() @@ -203,8 +214,10 @@ def main(): print('Error: --yes and --no are mutually exclusive') exit(1) + ctx = CmdCtx(args) + if args.cmd == 'configure': - configure() + configure(ctx) elif args.cmd == 'reset': reset() elif args.cmd == 'testdata':