stash
This commit is contained in:
parent
ec5ad720b9
commit
ad9109a8e0
5 changed files with 340 additions and 74 deletions
98
frontend/src/components/PropertyField.vue
Normal file
98
frontend/src/components/PropertyField.vue
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="taglist form-control form-control-lg">
|
||||||
|
<span class="badge bg-dark" v-for="(property, index) in value" :key="index">
|
||||||
|
{{ property.name }} =
|
||||||
|
<input type="number" class="form-control form-control-inline form-control-sm form-control-xsm bg-dark text-light border-dark"
|
||||||
|
id="propertyValue" name="propertyValue" placeholder="Enter value"
|
||||||
|
v-model="property.value">
|
||||||
|
<i class="bi bi-x-circle-fill" @click="removeProperty(index)"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<select class="form-select form-control form-control-lg" id="property" name="property" v-model="property">
|
||||||
|
<option v-for="(property, index) in availableProperties" :key="index" :value="property">{{ property }}</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" @click="addProperty">Add</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-control-inline {
|
||||||
|
display: inline;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taglist {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control-xsm {
|
||||||
|
min-height: calc(.6rem);
|
||||||
|
padding: .0rem .5rem;
|
||||||
|
font-size: .6rem;
|
||||||
|
border-radius: .1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=number].form-control-xsm{
|
||||||
|
padding: 0 0 0 .5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as BIcons from "bootstrap-icons-vue";
|
||||||
|
import {mapActions, mapState} from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "PropertyField",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
property: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
...BIcons
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
prop: "value",
|
||||||
|
event: "input"
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["properties"]),
|
||||||
|
availableProperties() {
|
||||||
|
return this.properties.filter(property => !this.localValue.map(p => p.name).includes(property));
|
||||||
|
},
|
||||||
|
localValue: {
|
||||||
|
get() {
|
||||||
|
return this.value;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit("input", value);
|
||||||
|
console.log("set", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(["fetchProperties"]),
|
||||||
|
addProperty() {
|
||||||
|
if (this.property !== "") {
|
||||||
|
this.localValue.push({name: this.property, value: 0});
|
||||||
|
this.property = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeProperty(index) {
|
||||||
|
this.localValue.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchProperties();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
79
frontend/src/components/TagField.vue
Normal file
79
frontend/src/components/TagField.vue
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="taglist form-control">
|
||||||
|
<span class="badge bg-dark" v-for="(tag, index) in value" :key="index">
|
||||||
|
{{ tag }}
|
||||||
|
<i class="bi bi-x-circle-fill" @click="removeTag(index)"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<select class="form-select" id="tag" name="tag" v-model="tag">
|
||||||
|
<option v-for="tag in availableTags" :key="tag" :value="tag">{{ tag }}</option>
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-outline-secondary" type="button" @click="addTag">Add</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.taglist {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import * as BIcons from "bootstrap-icons-vue";
|
||||||
|
import {mapActions, mapState} from "vuex";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TagField",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tag: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
...BIcons
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
prop: "value",
|
||||||
|
event: "input"
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(["tags"]),
|
||||||
|
availableTags() {
|
||||||
|
return this.tags.filter(tag => !this.localValue.includes(tag));
|
||||||
|
},
|
||||||
|
localValue: {
|
||||||
|
get() {
|
||||||
|
return this.value;
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.$emit("input", value);
|
||||||
|
console.log("set", value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(["fetchTags"]),
|
||||||
|
addTag() {
|
||||||
|
if (this.tag !== "") {
|
||||||
|
this.localValue.push(this.tag);
|
||||||
|
this.tag = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeTag(index) {
|
||||||
|
this.localValue.splice(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchTags();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -41,6 +41,33 @@ class ServerSet {
|
||||||
throw new Error('all servers failed')
|
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 = "http://" + server + target // TODO https
|
||||||
|
return await fetch(url, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...auth.buildAuthHeader(url)
|
||||||
|
},
|
||||||
|
credentials: 'omit',
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
}).catch(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 get(auth, target) {
|
async get(auth, target) {
|
||||||
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
if (!auth || typeof auth.buildAuthHeader !== 'function') {
|
||||||
throw new Error('no auth')
|
throw new Error('no auth')
|
||||||
|
@ -65,6 +92,58 @@ class ServerSet {
|
||||||
}
|
}
|
||||||
throw new Error('all servers failed')
|
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 = "http://" + server + target // TODO https
|
||||||
|
return await fetch(url, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
...auth.buildAuthHeader(url)
|
||||||
|
},
|
||||||
|
credentials: 'omit'
|
||||||
|
}).catch(err => this.unreachable_neighbors.unreachable(server)
|
||||||
|
).then(response => response.json())
|
||||||
|
} 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')
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
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 authMethod {
|
class authMethod {
|
||||||
|
@ -102,6 +181,12 @@ function createTokenAuth(token) {
|
||||||
}, context)
|
}, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {ServerSet, createSignAuth, createTokenAuth}
|
function createNullAuth() {
|
||||||
|
return new authMethod(() => {
|
||||||
|
return {}
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
export {ServerSet, createSignAuth, createTokenAuth, createNullAuth};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,13 @@ import {createStore} from 'vuex';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import FallBackResolver from "@/dns";
|
import FallBackResolver from "@/dns";
|
||||||
import NeighborsCache from "@/neigbors";
|
import NeighborsCache from "@/neigbors";
|
||||||
import {createSignAuth, createTokenAuth, ServerSet} from "@/federation";
|
import {createSignAuth, createTokenAuth, createNullAuth, ServerSet} from "@/federation";
|
||||||
|
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
state: {
|
state: {
|
||||||
local_loaded: false,
|
local_loaded: false,
|
||||||
|
last_load: {},
|
||||||
user: null,
|
user: null,
|
||||||
token: null,
|
token: null,
|
||||||
keypair: null,
|
keypair: null,
|
||||||
|
@ -20,6 +21,8 @@ export default createStore({
|
||||||
all_friends_servers: null,
|
all_friends_servers: null,
|
||||||
resolver: new FallBackResolver(),
|
resolver: new FallBackResolver(),
|
||||||
unreachable_neighbors: new NeighborsCache(),
|
unreachable_neighbors: new NeighborsCache(),
|
||||||
|
tags: [],
|
||||||
|
properties: [],
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setUser(state, user) {
|
setUser(state, user) {
|
||||||
|
@ -61,6 +64,14 @@ export default createStore({
|
||||||
console.log('setAllFriendsServers', servers)
|
console.log('setAllFriendsServers', servers)
|
||||||
state.all_friends_servers = servers;
|
state.all_friends_servers = servers;
|
||||||
},
|
},
|
||||||
|
setTags(state, tags) {
|
||||||
|
console.log('setTags', tags)
|
||||||
|
state.tags = tags;
|
||||||
|
},
|
||||||
|
setProperties(state, properties) {
|
||||||
|
console.log('setProperties', properties)
|
||||||
|
state.properties = properties;
|
||||||
|
},
|
||||||
logout(state) {
|
logout(state) {
|
||||||
state.user = null;
|
state.user = null;
|
||||||
state.token = null;
|
state.token = null;
|
||||||
|
@ -154,66 +165,6 @@ export default createStore({
|
||||||
async getFriendServers({state, dispatch, commit}, {username}) {
|
async getFriendServers({state, dispatch, commit}, {username}) {
|
||||||
return dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
|
return dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
|
||||||
},
|
},
|
||||||
/*async apiFederatedGet({state}, {host, target}) {
|
|
||||||
if (state.unreachable_neighbors.queryUnreachable(host)) {
|
|
||||||
throw new Error('unreachable neighbor')
|
|
||||||
}
|
|
||||||
if (!state.user || !state.keypair) {
|
|
||||||
throw new Error('no user or keypair')
|
|
||||||
}
|
|
||||||
const url = "http://" + host + target // TODO https
|
|
||||||
const signature = nacl.crypto_sign_detached(nacl.encode_utf8(url), state.keypair.signSk)
|
|
||||||
const auth = 'Signature ' + state.user + ':' + nacl.to_hex(signature)
|
|
||||||
return await fetch(url, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Authorization': auth
|
|
||||||
}
|
|
||||||
}).catch(err => state.unreachable_neighbors.unreachable(host)
|
|
||||||
).then(response => response.json())
|
|
||||||
},
|
|
||||||
async apiFederatedPost({state}, {host, target, data}) {
|
|
||||||
console.log('apiFederatedPost', host, target, data)
|
|
||||||
if (state.unreachable_neighbors.queryUnreachable(host)) {
|
|
||||||
throw new Error('unreachable neighbor')
|
|
||||||
}
|
|
||||||
if (!state.user || !state.keypair) {
|
|
||||||
throw new Error('no user or keypair')
|
|
||||||
}
|
|
||||||
const url = "http://" + host + target // TODO https
|
|
||||||
const json = JSON.stringify(data)
|
|
||||||
const signature = nacl.crypto_sign_detached(nacl.encode_utf8(url + json), state.keypair.signSk)
|
|
||||||
const auth = 'Signature ' + state.user + ':' + nacl.to_hex(signature)
|
|
||||||
return await fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': auth,
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: json
|
|
||||||
}).catch(err => state.unreachable_neighbors.unreachable(host)
|
|
||||||
).then(response => response.json())
|
|
||||||
},
|
|
||||||
async apiLocalGet({state}, {target}) {
|
|
||||||
const auth = state.token ? {'Authorization': 'Token ' + state.token} : {}
|
|
||||||
return await fetch(target, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: auth,
|
|
||||||
credentials: 'omit'
|
|
||||||
}).then(response => response.json())
|
|
||||||
},
|
|
||||||
async apiLocalPost({state}, {target, data}) {
|
|
||||||
const auth = state.token ? {'Authorization': 'Token ' + state.token} : {}
|
|
||||||
return await fetch(target, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...auth
|
|
||||||
},
|
|
||||||
credentials: 'omit',
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
}).then(response => response.json())
|
|
||||||
},*/
|
|
||||||
async fetchInventoryItems({commit, dispatch, getters}) {
|
async fetchInventoryItems({commit, dispatch, getters}) {
|
||||||
const servers = await dispatch('getHomeServers')
|
const servers = await dispatch('getHomeServers')
|
||||||
const items = await servers.get(getters.signAuth, '/api/inventory_items/')
|
const items = await servers.get(getters.signAuth, '/api/inventory_items/')
|
||||||
|
@ -224,6 +175,18 @@ export default createStore({
|
||||||
const servers = await dispatch('getAllFriendsServers')
|
const servers = await dispatch('getAllFriendsServers')
|
||||||
return await servers.get(getters.signAuth, '/api/inventory/search/?q=' + query)
|
return await servers.get(getters.signAuth, '/api/inventory/search/?q=' + query)
|
||||||
},
|
},
|
||||||
|
async createInventoryItem({state, dispatch, getters}, {item}) {
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
return await servers.post(getters.signAuth, '/api/inventory_items/', item)
|
||||||
|
},
|
||||||
|
async updateInventoryItem({state, dispatch, getters}, {item}) {
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
return await servers.patch(getters.signAuth, '/api/inventory_items/' + item.id + '/', item)
|
||||||
|
},
|
||||||
|
async deleteInventoryItem({state, dispatch, getters}, {item}) {
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
return await servers.delete(getters.signAuth, '/api/inventory_items/' + item.id + '/')
|
||||||
|
},
|
||||||
/*async searchInventoryItems() {
|
/*async searchInventoryItems() {
|
||||||
try {
|
try {
|
||||||
const servers = await this.fetchFriends().then(friends => friends.map(friend => this.lookupServer({username: friend.name})))
|
const servers = await this.fetchFriends().then(friends => friends.map(friend => this.lookupServer({username: friend.name})))
|
||||||
|
@ -249,7 +212,6 @@ export default createStore({
|
||||||
return await servers.get(getters.signAuth, '/api/friendrequests/')
|
return await servers.get(getters.signAuth, '/api/friendrequests/')
|
||||||
},
|
},
|
||||||
async requestFriend({state, dispatch, getters}, {username}) {
|
async requestFriend({state, dispatch, getters}, {username}) {
|
||||||
console.log('requesting friend ' + username)
|
|
||||||
if (username in state.friends) {
|
if (username in state.friends) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -261,7 +223,6 @@ export default createStore({
|
||||||
if (home_reply.status !== 'pending' || !home_reply.secret)
|
if (home_reply.status !== 'pending' || !home_reply.secret)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
console.log('home_reply', home_reply)
|
|
||||||
const befriendee_servers = await dispatch('getFriendServers', {username})
|
const befriendee_servers = await dispatch('getFriendServers', {username})
|
||||||
const ext_reply = befriendee_servers.post(getters.signAuth, '/api/friendrequests/', {
|
const ext_reply = befriendee_servers.post(getters.signAuth, '/api/friendrequests/', {
|
||||||
befriender: state.user,
|
befriender: state.user,
|
||||||
|
@ -269,16 +230,13 @@ export default createStore({
|
||||||
befriender_key: nacl.to_hex(state.keypair.signPk),
|
befriender_key: nacl.to_hex(state.keypair.signPk),
|
||||||
secret: home_reply.secret
|
secret: home_reply.secret
|
||||||
})
|
})
|
||||||
console.log('ext_reply', ext_reply)
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async acceptFriend({state, dispatch, getters}, {id, secret, befriender}) {
|
async acceptFriend({state, dispatch, getters}, {id, secret, befriender}) {
|
||||||
console.log('accepting friend ' + id)
|
|
||||||
const home_servers = await dispatch('getHomeServers')
|
const home_servers = await dispatch('getHomeServers')
|
||||||
const home_reply = await home_servers.post(getters.signAuth, '/api/friends/', {
|
const home_reply = await home_servers.post(getters.signAuth, '/api/friends/', {
|
||||||
friend_request_id: id, secret: secret
|
friend_request_id: id, secret: secret
|
||||||
})
|
})
|
||||||
console.log('home_reply', home_reply)
|
|
||||||
const ext_servers = await dispatch('getFriendServers', {username: befriender})
|
const ext_servers = await dispatch('getFriendServers', {username: befriender})
|
||||||
const ext_reply = await ext_servers.post(getters.signAuth, '/api/friendrequests/', {
|
const ext_reply = await ext_servers.post(getters.signAuth, '/api/friendrequests/', {
|
||||||
befriender: state.user,
|
befriender: state.user,
|
||||||
|
@ -286,13 +244,32 @@ export default createStore({
|
||||||
befriender_key: nacl.to_hex(state.keypair.signPk),
|
befriender_key: nacl.to_hex(state.keypair.signPk),
|
||||||
secret: secret
|
secret: secret
|
||||||
})
|
})
|
||||||
console.log('ext_reply', ext_reply)
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
async declineFriend({state, dispatch}, args) {
|
async declineFriend({state, dispatch}, args) {
|
||||||
// TODO implement
|
// TODO implement
|
||||||
console.log('declining friend ' + args)
|
console.log('declining friend ' + args)
|
||||||
},
|
},
|
||||||
|
async fetchTags({state, commit, dispatch, getters}) {
|
||||||
|
if(state.last_load.tags > Date.now() - 1000 * 60 * 60 * 24) {
|
||||||
|
return state.tags
|
||||||
|
}
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
const data = await servers.get(getters.signAuth, '/api/tags/')
|
||||||
|
commit('setTags', data)
|
||||||
|
state.last_load.tags = Date.now()
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
async fetchProperties({state, commit, dispatch, getters}) {
|
||||||
|
if(state.last_load.properties > Date.now() - 1000 * 60 * 60 * 24) {
|
||||||
|
return state.properties
|
||||||
|
}
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
const data = await servers.get(getters.signAuth, '/api/properties/')
|
||||||
|
commit('setProperties', data)
|
||||||
|
state.last_load.properties = Date.now()
|
||||||
|
return data
|
||||||
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
isLoggedIn(state) {
|
isLoggedIn(state) {
|
||||||
|
@ -316,6 +293,9 @@ export default createStore({
|
||||||
console.log('tokenAuth', state.token)
|
console.log('tokenAuth', state.token)
|
||||||
return createTokenAuth(state.token)
|
return createTokenAuth(state.token)
|
||||||
},
|
},
|
||||||
|
nullAuth(state) {
|
||||||
|
return createNullAuth({})
|
||||||
|
},
|
||||||
inventory_items(state) {
|
inventory_items(state) {
|
||||||
return Object.entries(state.item_map).reduce((acc, [url, items]) => {
|
return Object.entries(state.item_map).reduce((acc, [url, items]) => {
|
||||||
return acc.concat(items)
|
return acc.concat(items)
|
||||||
|
|
|
@ -6,6 +6,24 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">Create New Item</div>
|
<div class="card-header">Create New Item</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
<ul>
|
||||||
|
<li v-for="tag in item.tags" :key="tag">
|
||||||
|
{{ tag }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<label for="tags" class="form-label">Tags</label>
|
||||||
|
<tag-field :value="item.tags"></tag-field>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<ul>
|
||||||
|
<li v-for="property in item.properties" :key="property">
|
||||||
|
{{ property.name }}: {{ property.value }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<label for="property" class="form-label">Property</label>
|
||||||
|
<property-field :value="item.properties"></property-field>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="name" class="form-label">Name</label>
|
<label for="name" class="form-label">Name</label>
|
||||||
<input type="text" class="form-control" id="name" name="name"
|
<input type="text" class="form-control" id="name" name="name"
|
||||||
|
@ -33,7 +51,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<button type="submit" class="btn btn-primary" @click="createInventoryItem(item)">Add</button>
|
<button type="submit" class="btn btn-primary" @click="createInventoryItem(item)">Add
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,9 +63,10 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import * as BIcons from "bootstrap-icons-vue";
|
import * as BIcons from "bootstrap-icons-vue";
|
||||||
import BaseLayout from "@/components/BaseLayout.vue";
|
|
||||||
import {createApp} from "vue";
|
|
||||||
import {mapActions} from "vuex";
|
import {mapActions} from "vuex";
|
||||||
|
import BaseLayout from "@/components/BaseLayout.vue";
|
||||||
|
import TagField from "@/components/TagField.vue";
|
||||||
|
import PropertyField from "@/components/PropertyField.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "InventoryNew",
|
name: "InventoryNew",
|
||||||
|
@ -57,17 +77,21 @@ export default {
|
||||||
description: "",
|
description: "",
|
||||||
quantity: 0,
|
quantity: 0,
|
||||||
price: 0,
|
price: 0,
|
||||||
image: ""
|
image: "",
|
||||||
|
tags: [],
|
||||||
|
properties: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
BaseLayout,
|
BaseLayout,
|
||||||
|
TagField,
|
||||||
|
PropertyField,
|
||||||
...BIcons
|
...BIcons
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['createInventoryItem']),
|
...mapActions(['createInventoryItem'])
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue