Compare commits
3 commits
stable
...
jedi/dev/s
Author | SHA1 | Date | |
---|---|---|---|
af5cfedcab | |||
2b6b889f82 | |||
5217cdeeea |
18 changed files with 563 additions and 867 deletions
47
README.md
47
README.md
|
@ -1,21 +1,16 @@
|
||||||
# toolshed
|
# toolshed
|
||||||
|
|
||||||
## Development
|
## Installation / Development
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
git clone https://github.com/gr4yj3d1/toolshed.git
|
git clone https://github.com/gr4yj3d1/toolshed.git
|
||||||
```
|
```
|
||||||
|
|
||||||
or
|
or
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
git clone https://git.neulandlabor.de/j3d1/toolshed.git
|
git clone https://git.neulandlabor.de/j3d1/toolshed.git
|
||||||
```
|
```
|
||||||
|
|
||||||
all following development mode commands support auto-reloading and hot-reloading where applicable, they do not need to bw
|
### Backend
|
||||||
restarted after changes.
|
|
||||||
|
|
||||||
### Backend only
|
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
cd toolshed/backend
|
cd toolshed/backend
|
||||||
|
@ -25,11 +20,9 @@ pip install -r requirements.txt
|
||||||
python configure.py
|
python configure.py
|
||||||
python manage.py runserver 0.0.0.0:8000 --insecure
|
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
|
### Frontend
|
||||||
requests to the backend, then run the backend with just `python manage.py runserver` without the `--insecure` flag.
|
|
||||||
|
|
||||||
### Frontend only
|
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
cd toolshed/frontend
|
cd toolshed/frontend
|
||||||
|
@ -37,44 +30,14 @@ npm install
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docs only
|
### Docs
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
cd toolshed/docs
|
cd toolshed/docs
|
||||||
mkdocs serve
|
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/<version>/toolshed.zip` or
|
|
||||||
`https://github.com/gr4yj3d1/toolshed/archive/refs/tags/<version>.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
|
## CLI Client
|
||||||
|
|
||||||
|
|
|
@ -8,18 +8,9 @@ import dotenv
|
||||||
from django.db import transaction, IntegrityError
|
from django.db import transaction, IntegrityError
|
||||||
|
|
||||||
|
|
||||||
class CmdCtx:
|
def yesno(prompt, default=False):
|
||||||
|
if not sys.stdin.isatty():
|
||||||
def __init__(self, args):
|
|
||||||
self.args = args
|
|
||||||
|
|
||||||
def yesno(self, prompt, default=False):
|
|
||||||
if not sys.stdin.isatty() or self.args.noninteractive:
|
|
||||||
return default
|
return default
|
||||||
elif self.args.yes:
|
|
||||||
return True
|
|
||||||
elif self.args.no:
|
|
||||||
return False
|
|
||||||
yes = {'yes', 'y', 'ye'}
|
yes = {'yes', 'y', 'ye'}
|
||||||
no = {'no', 'n'}
|
no = {'no', 'n'}
|
||||||
|
|
||||||
|
@ -40,9 +31,9 @@ class CmdCtx:
|
||||||
print('Please respond with "yes" or "no"')
|
print('Please respond with "yes" or "no"')
|
||||||
|
|
||||||
|
|
||||||
def configure(ctx):
|
def configure():
|
||||||
if not os.path.exists('.env'):
|
if not os.path.exists('.env'):
|
||||||
if not ctx.yesno("the .env file does not exist, do you want to create it?", default=True):
|
if not yesno("the .env file does not exist, do you want to create it?", default=True):
|
||||||
print('Aborting')
|
print('Aborting')
|
||||||
exit(0)
|
exit(0)
|
||||||
if not os.path.exists('.env.dist'):
|
if not os.path.exists('.env.dist'):
|
||||||
|
@ -65,7 +56,7 @@ def configure(ctx):
|
||||||
current_hosts = os.getenv('ALLOWED_HOSTS')
|
current_hosts = os.getenv('ALLOWED_HOSTS')
|
||||||
print('Current ALLOWED_HOSTS: {}'.format(current_hosts))
|
print('Current ALLOWED_HOSTS: {}'.format(current_hosts))
|
||||||
|
|
||||||
if ctx.yesno("Do you want to add ALLOWED_HOSTS?"):
|
if yesno("Do you want to add ALLOWED_HOSTS?"):
|
||||||
hosts = input("Enter a comma-separated list of allowed hosts: ")
|
hosts = input("Enter a comma-separated list of allowed hosts: ")
|
||||||
joined_hosts = current_hosts + ',' + hosts if current_hosts else hosts
|
joined_hosts = current_hosts + ',' + hosts if current_hosts else hosts
|
||||||
dotenv.set_key('.env', 'ALLOWED_HOSTS', joined_hosts)
|
dotenv.set_key('.env', 'ALLOWED_HOSTS', joined_hosts)
|
||||||
|
@ -76,21 +67,20 @@ def configure(ctx):
|
||||||
django.setup()
|
django.setup()
|
||||||
|
|
||||||
if not os.path.exists('db.sqlite3'):
|
if not os.path.exists('db.sqlite3'):
|
||||||
if not ctx.yesno("No database found, do you want to create one?", default=True):
|
if not yesno("No database found, do you want to create one?", default=True):
|
||||||
print('Aborting')
|
print('Aborting')
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
call_command('migrate')
|
call_command('migrate')
|
||||||
|
|
||||||
if ctx.yesno("Do you want to create a superuser?"):
|
if yesno("Do you want to create a superuser?"):
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
call_command('createsuperuser')
|
call_command('createsuperuser')
|
||||||
|
|
||||||
call_command('collectstatic', '--no-input')
|
call_command('collectstatic', '--no-input')
|
||||||
|
|
||||||
if ctx.yesno("Do you want to import all categories, properties and tags contained in this repository?",
|
if yesno("Do you want to import all categories, properties and tags contained in this repository?", default=True):
|
||||||
default=True):
|
|
||||||
from hostadmin.serializers import CategorySerializer, PropertySerializer, TagSerializer
|
from hostadmin.serializers import CategorySerializer, PropertySerializer, TagSerializer
|
||||||
from hostadmin.models import ImportedIdentifierSets
|
from hostadmin.models import ImportedIdentifierSets
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
|
@ -206,7 +196,6 @@ def main():
|
||||||
parser = ArgumentParser(description='Toolshed Server Configuration')
|
parser = ArgumentParser(description='Toolshed Server Configuration')
|
||||||
parser.add_argument('--yes', '-y', help='Answer yes to all questions', action='store_true')
|
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('--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='?')
|
parser.add_argument('cmd', help='Command', default='configure', nargs='?')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
@ -214,10 +203,8 @@ def main():
|
||||||
print('Error: --yes and --no are mutually exclusive')
|
print('Error: --yes and --no are mutually exclusive')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
ctx = CmdCtx(args)
|
|
||||||
|
|
||||||
if args.cmd == 'configure':
|
if args.cmd == 'configure':
|
||||||
configure(ctx)
|
configure()
|
||||||
elif args.cmd == 'reset':
|
elif args.cmd == 'reset':
|
||||||
reset()
|
reset()
|
||||||
elif args.cmd == 'testdata':
|
elif args.cmd == 'testdata':
|
||||||
|
|
59
backend/shared_data/base.json
Normal file
59
backend/shared_data/base.json
Normal file
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
92
backend/shared_data/ee.json
Normal file
92
backend/shared_data/ee.json
Normal file
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
99
backend/shared_data/ee_packages.json
Normal file
99
backend/shared_data/ee_packages.json
Normal file
|
@ -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"
|
||||||
|
}
|
62
backend/shared_data/electrical.json
Normal file
62
backend/shared_data/electrical.json
Normal file
|
@ -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" }
|
||||||
|
]
|
||||||
|
}
|
82
backend/shared_data/it.json
Normal file
82
backend/shared_data/it.json
Normal file
|
@ -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" }
|
||||||
|
]
|
||||||
|
}
|
30
backend/shared_data/screws.json
Normal file
30
backend/shared_data/screws.json
Normal file
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
68
backend/shared_data/tools.json
Normal file
68
backend/shared_data/tools.json
Normal file
|
@ -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"}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,102 +0,0 @@
|
||||||
# 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/<version>/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/<version>.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 <domain>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update
|
|
||||||
|
|
||||||
``` bash
|
|
||||||
cd /var/www
|
|
||||||
wget https://git.neulandlabor.de/j3d1/toolshed/releases/download/<version>/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
|
|
||||||
```
|
|
|
@ -1,105 +0,0 @@
|
||||||
# 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/`.
|
|
|
@ -1,23 +0,0 @@
|
||||||
# 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).
|
|
|
@ -6,8 +6,47 @@ This is the documentation for the Toolshed project. It is a work in progress.
|
||||||
`#social` `#network` `#federation` `#decentralized` `#federated` `#socialnetwork` `#fediverse` `#community` `#hashtags`
|
`#social` `#network` `#federation` `#decentralized` `#federated` `#socialnetwork` `#fediverse` `#community` `#hashtags`
|
||||||
|
|
||||||
## Getting Started
|
## 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
|
||||||
|
```
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
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;
|
|
|
@ -1,324 +0,0 @@
|
||||||
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};
|
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,10 @@ import App from './App.vue'
|
||||||
import './scss/toolshed.scss'
|
import './scss/toolshed.scss'
|
||||||
|
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './store';
|
|
||||||
|
|
||||||
import _nacl from 'js-nacl';
|
import _nacl from 'js-nacl';
|
||||||
|
|
||||||
const app = createApp(App).use(store).use(BootstrapIconsPlugin);
|
const app = createApp(App).use(BootstrapIconsPlugin);
|
||||||
|
|
||||||
_nacl.instantiate((nacl) => {
|
_nacl.instantiate((nacl) => {
|
||||||
window.nacl = nacl
|
window.nacl = nacl
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
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;
|
|
|
@ -1,132 +0,0 @@
|
||||||
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({})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
Loading…
Reference in a new issue