stash
This commit is contained in:
		
							parent
							
								
									fda9f8e70f
								
							
						
					
					
						commit
						5c6d560680
					
				
					 9 changed files with 167 additions and 69 deletions
				
			
		|  | @ -6,7 +6,7 @@ | |||
|                 <a class="sidebar-toggle d-flex" @click="toggleSidebar"> | ||||
|                     <i class="hamburger align-self-center"></i> | ||||
|                 </a> | ||||
|                 <SearchBox/> | ||||
|                 <SearchBox v-if="!hideSearch"/> | ||||
|                 <div class="navbar-collapse collapse"> | ||||
|                     <ul class="navbar-nav navbar-align"> | ||||
|                         <Notifications :notifications="notifications"/> | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <template> | ||||
|     <form class="d-none d-sm-inline-block"> | ||||
|     <form class="d-none d-sm-inline-block" @submit.prevent="search"> | ||||
|         <div class="input-group input-group-navbar"> | ||||
|             <input type="text" class="form-control" placeholder="Search…" aria-label="Search" v-model="query" ref="search-text" /> | ||||
|             <button class="btn" type="button" @click.prevent="search"> | ||||
|  | @ -18,6 +18,10 @@ export default { | |||
|     components: { | ||||
|         ...BIcons | ||||
|     }, | ||||
|     model: { | ||||
|         prop: "query", | ||||
|         event: "change" | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             query: "" | ||||
|  | @ -31,6 +35,12 @@ export default { | |||
|                 this.$refs["search-text"].focus(); | ||||
|         } | ||||
|     }, | ||||
|     watch: { | ||||
|         query() { | ||||
|             //emit event | ||||
|             this.$emit("change", this.query); | ||||
|         } | ||||
|     }, | ||||
|     mounted() { | ||||
|         //console.log(this.$route) | ||||
|         //console.log(this.$route.params.query) | ||||
|  |  | |||
|  | @ -179,9 +179,10 @@ class ServerSet { | |||
|                 } | ||||
|                 const url = "https://" + server + target // TODO https
 | ||||
|                 return await fetch(url, { | ||||
|                     method: 'GET', | ||||
|                     method: 'PUT', | ||||
|                     headers: { | ||||
|                         ...auth.buildAuthHeader(url) | ||||
|                         'Content-Type': 'application/json', | ||||
|                         ...auth.buildAuthHeader(url, data) | ||||
|                     }, | ||||
|                     credentials: 'omit' | ||||
|                 }).catch(err => { | ||||
|  | @ -190,7 +191,7 @@ class ServerSet { | |||
|                     } | ||||
|                 ) | ||||
|             } catch (e) { | ||||
|                 console.error('get from server failed', server, e) | ||||
|                 console.error('put to server failed', server, e) | ||||
|             } | ||||
|         } | ||||
|         throw new Error('all servers failed') | ||||
|  | @ -212,43 +213,90 @@ class ServerSet { | |||
|                         ...auth.buildAuthHeader(url) | ||||
|                     }, | ||||
|                     credentials: 'omit' | ||||
|                 }).catch(err => this.unreachable_neighbors.unreachable(server) | ||||
|                 ).then(response => response.json()) | ||||
|                 }).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') | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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 post(auth, target, data) { | ||||
|         try { | ||||
|             return await this.serverSets.reduce(async (acc, serverset) => { | ||||
|                 return 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 await serverset.patch(auth, target, data) | ||||
|             }, Promise.resolve()) | ||||
|         } catch (e) { | ||||
|             throw new Error('all servers failed') | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async get(auth, target) { | ||||
|         try { | ||||
|             return await this.serverSets.reduce(async (acc, serverset) => { | ||||
|                 return await serverset.get(auth, target) | ||||
|             }, Promise.resolve()) | ||||
|         } catch (e) { | ||||
|             throw new Error('all servers failed') | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async delete(auth, target) { | ||||
|         try { | ||||
|             return await this.serverSets.reduce(async (acc, serverset) => { | ||||
|                 return await serverset.delete(auth, target) | ||||
|             }, Promise.resolve()) | ||||
|         } catch (e) { | ||||
|             throw new Error('all servers failed') | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async put(auth, target, data) { | ||||
|         if (!auth || typeof auth.buildAuthHeader !== 'function') { | ||||
|             throw new Error('no auth') | ||||
|         try { | ||||
|             return await this.serverSets.reduce(async (acc, serverset) => { | ||||
|                 return await serverset.put(auth, target, data) | ||||
|             }, Promise.resolve()) | ||||
|         } catch (e) { | ||||
|             throw new Error('all servers failed') | ||||
|         } | ||||
|         for (const server of this.servers) { | ||||
|             try { | ||||
|                 if (this.unreachable_neighbors.queryUnreachable(server)) { | ||||
|                     continue | ||||
|                 } | ||||
|                 const url = "http://" + 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 => 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') | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class ServerSetUnion { | ||||
|     constructor(serverSets) { | ||||
|         if (!serverSets || !Array.isArray(serverSets)) { | ||||
|  | @ -351,7 +399,6 @@ function createSignAuth(username, signKey) { | |||
|     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) | ||||
|         console.log('sign', nacl.to_hex(signature), url, json) | ||||
|         return {'Authorization': 'Signature ' + username + ':' + nacl.to_hex(signature)} | ||||
|     }, context) | ||||
| } | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ const routes = [ | |||
|     {path: '/inventory/:id/edit', component: InventoryEdit, meta: {requiresAuth: true}, props: true}, | ||||
|     {path: '/inventory/new', component: InventoryNew, meta: {requiresAuth: true}}, | ||||
|     {path: '/friends', component: Friends, meta: {requiresAuth: true}}, | ||||
|     {path: '/search/:query', component: Search, meta: {requiresAuth: true}}, | ||||
|     {path: '/search/:query', component: Search, meta: {requiresAuth: true}, props: true}, | ||||
|     {path: '/login', component: Login, meta: {requiresAuth: false}}, | ||||
|     {path: '/register', component: Register, meta: {requiresAuth: false}}, | ||||
|     {path: '/:pathMatch(.*)*', redirect: '/'} | ||||
|  |  | |||
|  | @ -52,7 +52,6 @@ export default createStore({ | |||
|             localStorage.setItem('remember', remember); | ||||
|         }, | ||||
|         setInventoryItems(state, {url, items}) { | ||||
|             console.log('setInventoryItems', url, items) | ||||
|             state.item_map[url] = items; | ||||
|         }, | ||||
|         setFriends(state, friends) { | ||||
|  | @ -128,7 +127,7 @@ export default createStore({ | |||
|             if (domain === 'localhost') | ||||
|                 return ['127.0.0.1:8000']; | ||||
|             if (domain === 'example.com') | ||||
|                 return ['10.23.42.128:8000']; | ||||
|                 return ['10.23.42.128:8000','10.23.42.128:8000']; | ||||
|             if (domain === 'example.jedi') | ||||
|                 return ['10.23.42.128:8000']; | ||||
|             if (domain === 'example2.com') | ||||
|  | @ -195,6 +194,10 @@ export default createStore({ | |||
|             dispatch('fetchInventoryItems') | ||||
|             return ret | ||||
|         }, | ||||
|         async fetchSearchResults({state, dispatch, getters}, {query}) { | ||||
|             const servers = await dispatch('getAllFriendsServers') | ||||
|             return await servers.get(getters.signAuth, '/api/search/?query=' + query) | ||||
|         }, | ||||
|         async fetchFriends({commit, dispatch, getters, state}) { | ||||
|             const servers = await dispatch('getHomeServers') | ||||
|             const data = await servers.get(getters.signAuth, '/api/friends/') | ||||
|  |  | |||
|  | @ -104,13 +104,10 @@ export default { | |||
|         ...BIcons | ||||
|     }, | ||||
|     computed: { | ||||
|         ...mapGetters(["inventory_items"]), | ||||
|         username() { | ||||
|             return this.$route.params.username | ||||
|         } | ||||
|         ...mapGetters(["inventory_items"]) | ||||
|     }, | ||||
|     methods: { | ||||
|         ...mapActions(["fetchInventoryItems"]), | ||||
|         ...mapActions(["fetchInventoryItems", "deleteInventoryItem"]), | ||||
|     }, | ||||
|     async mounted() { | ||||
|         await this.fetchInventoryItems() | ||||
|  |  | |||
|  | @ -53,19 +53,6 @@ import {mapActions, mapGetters} from "vuex"; | |||
| 
 | ||||
| export default { | ||||
|     name: "InventoryDetail", | ||||
|     data() { | ||||
|         return { | ||||
|             item: { | ||||
|                 name: "", | ||||
|                 description: "", | ||||
|                 quantity: 0, | ||||
|                 price: 0, | ||||
|                 image: "", | ||||
|                 tags: [], | ||||
|                 properties: [] | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     components: { | ||||
|         PropertyField, TagField, | ||||
|         BaseLayout, | ||||
|  |  | |||
|  | @ -6,6 +6,16 @@ | |||
|                     <div class="card"> | ||||
|                         <div class="card-header">Create New Item</div> | ||||
|                         <div class="card-body"> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="name" class="form-label">Name</label> | ||||
|                                 <input type="text" class="form-control" id="name" name="name" | ||||
|                                        placeholder="Enter item name" v-model="item.name"> | ||||
|                             </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="description" class="form-label">Description</label> | ||||
|                                 <textarea class="form-control" id="description" name="description" | ||||
|                                           placeholder="Enter description" v-model="item.description"></textarea> | ||||
|                             </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <ul> | ||||
|                                     <li v-for="tag in item.tags" :key="tag"> | ||||
|  | @ -24,16 +34,6 @@ | |||
|                                 <label for="property" class="form-label">Property</label> | ||||
|                                 <property-field :value="item.properties"></property-field> | ||||
|                             </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="name" class="form-label">Name</label> | ||||
|                                 <input type="text" class="form-control" id="name" name="name" | ||||
|                                        placeholder="Enter item name" v-model="item.name"> | ||||
|                             </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="description" class="form-label">Description</label> | ||||
|                                 <textarea class="form-control" id="description" name="description" | ||||
|                                           placeholder="Enter description" v-model="item.description"></textarea> | ||||
|                             </div> | ||||
|                             <div class="mb-3"> | ||||
|                                 <label for="tags" class="form-label">Tags</label> | ||||
|                                 <tag-field :value="item.tags"></tag-field> | ||||
|  |  | |||
|  | @ -1,18 +1,48 @@ | |||
| <template> | ||||
|     <BaseLayout> | ||||
|     <BaseLayout hide-search> | ||||
|         <main class="content"> | ||||
|             <div class="container-fluid p-0"> | ||||
|                 <h1 class="h3 mb-3">Blank Page</h1> | ||||
|                 <h1 class="h3 mb-3">Search Inventories</h1> | ||||
|                 <div class="row"> | ||||
|                     <div class="col-md-3"> | ||||
|                         <div class="card"> | ||||
|                             <SearchBox @change="filterResults" /> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="col-12"> | ||||
|                         <div class="card"> | ||||
|                             <div class="card-header"> | ||||
|                                 <h5 class="card-title mb-0">Empty card</h5> | ||||
|                                 <h5 class="card-title mb-0">Results for "{{ query }}"</h5> | ||||
|                             </div> | ||||
|                             <div class="card-body"> | ||||
|                                 <div class="logo"> | ||||
|                                     <img src="/src/assets/icons/toolshed-48x48.png" alt="Toolshed logo"> | ||||
|                                 </div> | ||||
|                                 <table class="table table-striped"> | ||||
|                                     <thead> | ||||
|                                     <tr> | ||||
|                                         <th style="width:40%;">Name</th> | ||||
|                                         <th style="width:25%">Owner</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 search_results" :key="item.id"> | ||||
|                                         <td> | ||||
|                                             <router-link :to="`/inventory/${item.id}`">{{ item.name }}</router-link> | ||||
|                                         </td> | ||||
|                                         <td>{{ item.owner }}</td> | ||||
|                                         <td class="d-none d-md-table-cell">{{ item.owned_quantity }}</td> | ||||
|                                         <td class="table-action"> | ||||
|                                             <!--<router-link :to="`/inventory/${item.id}/edit`"> | ||||
|                                                 <b-icon-pencil-square></b-icon-pencil-square> | ||||
|                                             </router-link> | ||||
|                                             <a :href="`/inventory/${item.id}/delete`" | ||||
|                                                @click.prevent="deleteInventoryItem(item)"> | ||||
|                                                 <b-icon-trash></b-icon-trash> | ||||
|                                             </a>--> | ||||
|                                         </td> | ||||
|                                     </tr> | ||||
|                                     </tbody> | ||||
|                                 </table> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|  | @ -23,16 +53,40 @@ | |||
| </template> | ||||
| 
 | ||||
| <script> | ||||
| import {mapGetters, mapMutations} from 'vuex'; | ||||
| import {mapActions} from 'vuex'; | ||||
| import * as BIcons from "bootstrap-icons-vue"; | ||||
| import BaseLayout from "@/components/BaseLayout.vue"; | ||||
| import SearchBox from "@/components/SearchBox.vue"; | ||||
| 
 | ||||
| export default { | ||||
|     name: 'Search', | ||||
|     components: { | ||||
|         SearchBox, | ||||
|         ...BIcons, | ||||
|         BaseLayout | ||||
|     }, | ||||
|     props: { | ||||
|         query: { | ||||
|             type: String, | ||||
|             required: true | ||||
|         } | ||||
|     }, | ||||
|     data() { | ||||
|         return { | ||||
|             search_results: [], | ||||
|         } | ||||
|     }, | ||||
|     methods: { | ||||
|         ...mapActions(['fetchSearchResults']), | ||||
|         filterResults(query) { | ||||
|             this.localQuery = query; | ||||
|         } | ||||
|     }, | ||||
|     async mounted() { | ||||
|         this.fetchSearchResults({query: this.query}).then((results) => { | ||||
|             this.search_results = results; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| </script> | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue