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')
|
||||
}
|
||||
}
|
||||
|
||||
async put(auth, target, data) {
|
||||
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
||||
throw new Error('no auth')
|
||||
class ServerSetUnion {
|
||||
constructor(serverSets) {
|
||||
if (!serverSets || !Array.isArray(serverSets)) {
|
||||
throw new Error('no serverSets')
|
||||
}
|
||||
for (const server of this.servers) {
|
||||
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 {
|
||||
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())
|
||||
return await this.serverSets.reduce(async (acc, serverset) => {
|
||||
return await serverset.post(auth, target, data)
|
||||
}, Promise.resolve())
|
||||
} catch (e) {
|
||||
console.error('put to server failed', server, 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) {
|
||||
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')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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…
Reference in a new issue