diff --git a/deploy/dev/instance_a/testdata.py b/deploy/dev/instance_a/testdata.py new file mode 100644 index 0000000..4a5cfbd --- /dev/null +++ b/deploy/dev/instance_a/testdata.py @@ -0,0 +1,108 @@ +import base64 + + +def create_test_data(): + print('Creating test data for instance A') + + from authentication.models import ToolshedUser + admin = None + try: + if ToolshedUser.objects.filter(username='admin').exists(): + admin = ToolshedUser.objects.get(username='admin') + else: + admin = ToolshedUser.objects.create_superuser('admin', 'admin@localhost', '') + admin.set_password('j7Th5TfCGUZmQ7U') + admin.save() + print('Created {}@{} with private key {} and public key {}'.format(admin.username, admin.domain, + admin.private_key, + admin.public_identity.public_key)) + except Exception as e: + print('Admin user already exists, skipping') + + from hostadmin.models import Domain + try: + domain1 = Domain.objects.create(name='localhost', owner=admin, open_registration=False) + domain1.save() + print('Created domain {} with closed registration'.format(domain1.name)) + except Exception as e: + print('Domain localhost already exists, skipping') + + domain2 = None + try: + if Domain.objects.filter(name='a.localhost').exists(): + domain2 = Domain.objects.get(name='a.localhost') + domain2 = Domain.objects.create(name='a.localhost', owner=admin, open_registration=True) + domain2.save() + print('Created domain {} with open registration'.format(domain2.name)) + except Exception as e: + print('Domain b.localhost already exists, skipping') + + user = None + try: + if ToolshedUser.objects.filter(username='test_a').exists(): + user = ToolshedUser.objects.get(username='test_a') + user = ToolshedUser.objects.create_user('test_a', 'testa@example.com', '', domain=domain2.name, + private_key='4ab79601edc400fd0263c7d5fd045ea7f5de28f1dd8905e2479f96893f3257d8') + user.set_password('test_a') + user.save() + print('Created user {}@{} with private key {} and public key {}'.format(user.username, user.domain, + user.private_key, + user.public_identity.public_key)) + except Exception as e: + print('User foobar already exists, skipping') + + from authentication.models import KnownIdentity + identity = None + try: + if KnownIdentity.objects.filter(username='test_b').exists(): + identity = KnownIdentity.objects.get(username='test_b') + identity = KnownIdentity.objects.create(username='test_b', domain='b.localhost', + public_key='4b0dffe21764c591762615ef84cfea7fd4055fab3e9f58ae077fd8c79c34af91') + # pk '2ec1e7d5f5b8d5f87233944970d57f942095fe9e6c4fc49edde61fcd3fb1bf40' + identity.save() + print('Created identity {}@{} with public key {}'.format(identity.username, identity.domain, + identity.public_key)) + except Exception as e: + print('Identity already exists, skipping') + + from toolshed.models import Category + category1 = None + try: + if Category.objects.filter(name='Test Category').exists(): + category1 = Category.objects.get(name='Test Category') + category1 = Category.objects.get_or_create(name='Test Category', origin='test')[0] + except Exception as e: + print('Category already exists, skipping') + + from toolshed.models import InventoryItem + item1 = None + try: + if InventoryItem.objects.filter(name='Test Item').exists(): + item1 = InventoryItem.objects.get(name='Test Item') + item1 = InventoryItem.objects.create(name='Test Item', description='This is a test item', category=category1, + availability_policy='rent', owned_quantity=1, owner=user) + item1.save() + except Exception as e: + print('Item already exists, skipping') + + try: + item2 = InventoryItem.objects.create(name='Test Item 2', description='This is a test item', category=category1, + availability_policy='share', owned_quantity=1, owner=user) + item2.save() + print('Created test items with IDs "{}" and "{}"'.format(item1.id, item2.id)) + except Exception as e: + print('Item already exists, skipping') + + try: + user.friends.add(identity) + print('Added identity {} to friends list of user {}'.format(identity.username, user.username)) + except Exception as e: + print('Identity already in friends list, skipping') + + from files.models import File + try: + file1 = File.objects.create(mime_type='text/plain', data=base64.b64encode(b'testcontent1').decode('utf-8')) + file1.save() + print(f'Created file at /{file1.hash[:2]}/{file1.hash[2:4]}/{file1.hash[4:6]}/{file1.hash[6:]}') + except Exception as e: + print('File already exists, skipping') diff --git a/deploy/dev/instance_b/testdata.py b/deploy/dev/instance_b/testdata.py new file mode 100644 index 0000000..2a30680 --- /dev/null +++ b/deploy/dev/instance_b/testdata.py @@ -0,0 +1,108 @@ +import base64 + + +def create_test_data(): + print('Creating test data for instance B') + + from authentication.models import ToolshedUser + admin = None + try: + if ToolshedUser.objects.filter(username='admin').exists(): + admin = ToolshedUser.objects.get(username='admin') + else: + admin = ToolshedUser.objects.create_superuser('admin', 'admin@localhost', '') + admin.set_password('j7Th5TfCGUZmQ7U') + admin.save() + print('Created {}@{} with private key {} and public key {}'.format(admin.username, admin.domain, + admin.private_key, + admin.public_identity.public_key)) + except Exception as e: + print('Admin user already exists, skipping') + + from hostadmin.models import Domain + try: + domain1 = Domain.objects.create(name='localhost', owner=admin, open_registration=False) + domain1.save() + print('Created domain {} with closed registration'.format(domain1.name)) + except Exception as e: + print('Domain localhost already exists, skipping') + + domain2 = None + try: + if Domain.objects.filter(name='b.localhost').exists(): + domain2 = Domain.objects.get(name='b.localhost') + domain2 = Domain.objects.create(name='b.localhost', owner=admin, open_registration=True) + domain2.save() + print('Created domain {} with open registration'.format(domain2.name)) + except Exception as e: + print('Domain b.localhost already exists, skipping') + + user = None + try: + if ToolshedUser.objects.filter(username='test_b').exists(): + user = ToolshedUser.objects.get(username='test_b') + user = ToolshedUser.objects.create_user('test_b', 'testb@example.com', '', domain=domain2.name, + private_key='2ec1e7d5f5b8d5f87233944970d57f942095fe9e6c4fc49edde61fcd3fb1bf40') + user.set_password('test_b') + user.save() + print('Created user {}@{} with private key {} and public key {}'.format(user.username, user.domain, + user.private_key, + user.public_identity.public_key)) + except Exception as e: + print('User foobar already exists, skipping') + + from authentication.models import KnownIdentity + identity = None + try: + if KnownIdentity.objects.filter(username='test_a').exists(): + identity = KnownIdentity.objects.get(username='test_a') + identity = KnownIdentity.objects.create(username='test_a', domain='a.localhost', + public_key='cdcf6f1897b0ab3a123047d9b707d4123f01daf41921dbe787d1d0697f0ee42b') + # pk '4ab79601edc400fd0263c7d5fd045ea7f5de28f1dd8905e2479f96893f3257d8' + identity.save() + print('Created identity {}@{} with public key {}'.format(identity.username, identity.domain, + identity.public_key)) + except Exception as e: + print('Identity already exists, skipping') + + from toolshed.models import Category + category1 = None + try: + if Category.objects.filter(name='Test Category').exists(): + category1 = Category.objects.get(name='Test Category') + category1 = Category.objects.get_or_create(name='Test Category', origin='test')[0] + except Exception as e: + print('Category already exists, skipping') + + from toolshed.models import InventoryItem + item1 = None + try: + if InventoryItem.objects.filter(name='Test Item 3').exists(): + item1 = InventoryItem.objects.get(name='Test Item 3') + item1 = InventoryItem.objects.create(name='Test Item 3', description='This is a test item', category=category1, + availability_policy='sell', owned_quantity=1, owner=user) + item1.save() + except Exception as e: + print('Item already exists, skipping') + + try: + item2 = InventoryItem.objects.create(name='Test Item 4', description='This is a test item', category=category1, + availability_policy='lend', owned_quantity=1, owner=user) + item2.save() + print('Created test items with IDs "{}" and "{}"'.format(item1.id, item2.id)) + except Exception as e: + print('Item already exists, skipping') + + try: + user.friends.add(identity) + print('Added identity {} to friends list of user {}'.format(identity.username, user.username)) + except Exception as e: + print('Identity already in friends list, skipping') + + from files.models import File + try: + file1 = File.objects.create(mime_type='text/plain', data=base64.b64encode(b'testcontent1').decode('utf-8')) + file1.save() + print(f'Created file at /{file1.hash[:2]}/{file1.hash[2:4]}/{file1.hash[4:6]}/{file1.hash[6:]}') + except Exception as e: + print('File already exists, skipping') diff --git a/deploy/docker-compose.override.yml b/deploy/docker-compose.override.yml index 30b4de9..6ed6978 100644 --- a/deploy/docker-compose.override.yml +++ b/deploy/docker-compose.override.yml @@ -8,6 +8,7 @@ services: volumes: - ../backend:/code - ../deploy/dev/instance_a/a.env:/code/.env + - ../deploy/dev/instance_a/testdata.py:/code/testdata.py - ../deploy/dev/instance_a/a.sqlite3:/code/db.sqlite3 expose: - 8000 @@ -20,6 +21,7 @@ services: volumes: - ../backend:/code - ../deploy/dev/instance_b/b.env:/code/.env + - ../deploy/dev/instance_b/testdata.py:/code/testdata.py - ../deploy/dev/instance_b/b.sqlite3:/code/db.sqlite3 expose: - 8000 @@ -30,11 +32,11 @@ services: context: ../frontend/ dockerfile: ../deploy/dev/Dockerfile.frontend volumes: - - ../frontend:/app:ro + - ../frontend:/app - /app/node_modules expose: - 5173 - command: npm run dev -- --host + command: bash -c "npm install && npm run dev -- --host" wiki: build: diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue index 136556f..38c4b13 100644 --- a/frontend/src/components/Sidebar.vue +++ b/frontend/src/components/Sidebar.vue @@ -9,6 +9,12 @@ <li class="sidebar-header"> Tools & Components </li> + <li class="sidebar-item"> + <router-link to="/inventory" class="sidebar-link"> + <b-icon-archive class="bi-valign-middle"></b-icon-archive> + <span class="align-middle">Inventory</span> + </router-link> + </li> <li class="sidebar-item"> <router-link to="/friends" class="sidebar-link"> <b-icon-people class="bi-valign-middle"></b-icon-people> diff --git a/frontend/src/dns.js b/frontend/src/dns.js index f07ea22..9446ccb 100644 --- a/frontend/src/dns.js +++ b/frontend/src/dns.js @@ -13,9 +13,13 @@ function get_prefered_server() { 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; + try { + const servers = JSON.parse(request.responseText); + if (servers && servers.length > 0) { + return servers; + } + } catch (e) { + console.error(e); } } return ['1.1.1.1', '8.8.8.8']; diff --git a/frontend/src/router.js b/frontend/src/router.js index ee7cf6e..d8d97fa 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -4,10 +4,12 @@ import Login from '@/views/Login.vue'; import Register from '@/views/Register.vue'; import store from '@/store'; import Friends from "@/views/Friends.vue"; +import Inventory from '@/views/Inventory.vue'; const routes = [ {path: '/', component: Dashboard, meta: {requiresAuth: true}}, + {path: '/inventory', component: Inventory, meta: {requiresAuth: true}}, {path: '/friends', component: Friends, meta: {requiresAuth: true}}, {path: '/login', component: Login, meta: {requiresAuth: false}}, {path: '/register', component: Register, meta: {requiresAuth: false}}, diff --git a/frontend/src/store.js b/frontend/src/store.js index 071004d..5b4450a 100644 --- a/frontend/src/store.js +++ b/frontend/src/store.js @@ -13,10 +13,13 @@ export default createStore({ token: null, keypair: null, remember: false, + friends: [], + item_map: {}, home_servers: null, all_friends_servers: null, resolver: new FallBackResolver(), unreachable_neighbors: new NeighborsCache(), + availability_policies: [], }, mutations: { setUser(state, user) { @@ -43,6 +46,9 @@ export default createStore({ } localStorage.setItem('remember', remember); }, + setInventoryItems(state, {url, items}) { + state.item_map[url] = items; + }, setFriends(state, friends) { state.friends = friends; }, @@ -52,6 +58,9 @@ export default createStore({ setAllFriendsServers(state, servers) { state.all_friends_servers = servers; }, + setAvailabilityPolicies(state, availability_policies) { + state.availability_policies = availability_policies; + }, logout(state) { state.user = null; state.token = null; @@ -128,6 +137,13 @@ export default createStore({ async getFriendServers({state, dispatch, commit}, {username}) { return dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors)) }, + async fetchInventoryItems({commit, dispatch, getters}) { + const servers = await dispatch('getHomeServers') + const items = await servers.get(getters.signAuth, '/api/inventory_items/') + items.map(item => item.files.map(file => file.owner = item.owner)) + commit('setInventoryItems', {url: '/', items}) + return items + }, async fetchFriends({commit, dispatch, getters, state}) { const servers = await dispatch('getHomeServers') const data = await servers.get(getters.signAuth, '/api/friends/') @@ -205,5 +221,13 @@ export default createStore({ nullAuth(state) { return createNullAuth({}) }, + inventory_items(state) { + return state.item_map['/'] || [] + }, + loaded_items(state) { + return Object.entries(state.item_map).reduce((acc, [url, items]) => { + return acc.concat(items) + }, []) + }, } }) \ No newline at end of file diff --git a/frontend/src/views/Inventory.vue b/frontend/src/views/Inventory.vue new file mode 100644 index 0000000..9159df9 --- /dev/null +++ b/frontend/src/views/Inventory.vue @@ -0,0 +1,70 @@ +<template> + <BaseLayout> + <main class="content"> + <div class="container-fluid p-0"> + <div class="row"> + <div class="col-12 col-xl-6"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title">{{ user }}'s Inventory</h5> + </div> + <table class="table table-striped"> + <thead> + <tr> + <th style="width:40%;">Name</th> + <th style="width:25%">Availability Policy</th> + <th class="d-none d-md-table-cell" style="width:25%">Amount</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + <tr v-for="item in inventory_items" :key="item.id"> + <td> + {{ item.name }} + </td> + <td class="d-none d-md-table-cell">{{ item.availability_policy }}</td> + <td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td> + <td class="table-action"> + </td> + </tr> + </tbody> + </table> + </div> + <div class="card"> + <button class="btn" @click="fetchInventoryItems">Refresh</button> + <router-link to="/inventory/new" class="btn btn-primary">Add</router-link> + </div> + </div> + </div> + </div> + </main> + </BaseLayout> +</template> + +<script> +import {mapActions, mapGetters, mapMutations, mapState} from "vuex"; +import * as BIcons from "bootstrap-icons-vue"; +import BaseLayout from "@/components/BaseLayout.vue"; + +export default { + name: "Inventory", + components: { + BaseLayout, + ...BIcons + }, + computed: { + ...mapGetters(["inventory_items", "loaded_items"]), + ...mapState(["user"]), + }, + methods: { + ...mapActions(["fetchInventoryItems", "deleteInventoryItem"]), + }, + async mounted() { + await this.fetchInventoryItems() + } +} +</script> + +<style scoped> + +</style> \ No newline at end of file