stash
This commit is contained in:
parent
21cb4018a3
commit
06f7c515ef
4 changed files with 237 additions and 83 deletions
107
frontend/src/federation.js
Normal file
107
frontend/src/federation.js
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
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 = servers;
|
||||||
|
this.unreachable_neighbors = unreachable_neighbors;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(server) {
|
||||||
|
this.servers.push(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = "http://" + server + target // TODO https
|
||||||
|
return await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
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('post to server failed', server, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('all servers failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = "http://" + server + target // TODO https
|
||||||
|
return await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
...auth.buildAuthHeader(url)
|
||||||
|
},
|
||||||
|
credentials: 'omit'
|
||||||
|
}).catch(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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {ServerSet, createSignAuth, createTokenAuth}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ 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 {useRoute} from "vue-router";
|
import {createSignAuth, createTokenAuth, ServerSet} from "@/federation";
|
||||||
|
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
|
@ -15,7 +15,8 @@ export default createStore({
|
||||||
item_map: {},
|
item_map: {},
|
||||||
//notifications: [],
|
//notifications: [],
|
||||||
messages: [],
|
messages: [],
|
||||||
home_server: null,
|
home_servers: null,
|
||||||
|
all_friends_servers: null,
|
||||||
resolver: new FallBackResolver(),
|
resolver: new FallBackResolver(),
|
||||||
unreachable_neighbors: new NeighborsCache(),
|
unreachable_neighbors: new NeighborsCache(),
|
||||||
},
|
},
|
||||||
|
@ -45,11 +46,20 @@ export default createStore({
|
||||||
localStorage.setItem('remember', remember);
|
localStorage.setItem('remember', remember);
|
||||||
},
|
},
|
||||||
setInventoryItems(state, {url, items}) {
|
setInventoryItems(state, {url, items}) {
|
||||||
|
console.log('setInventoryItems', url, items)
|
||||||
state.item_map[url] = items;
|
state.item_map[url] = items;
|
||||||
},
|
},
|
||||||
setFriends(state, friends) {
|
setFriends(state, friends) {
|
||||||
state.friends = friends;
|
state.friends = friends;
|
||||||
},
|
},
|
||||||
|
setHomeServers(state, home_servers) {
|
||||||
|
console.log('setHomeServer', home_servers)
|
||||||
|
state.home_servers = home_servers;
|
||||||
|
},
|
||||||
|
setAllFriendsServers(state, servers) {
|
||||||
|
console.log('setAllFriendsServers', servers)
|
||||||
|
state.all_friends_servers = servers;
|
||||||
|
},
|
||||||
logout(state) {
|
logout(state) {
|
||||||
state.user = null;
|
state.user = null;
|
||||||
state.token = null;
|
state.token = null;
|
||||||
|
@ -72,6 +82,7 @@ export default createStore({
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
router.push('/');
|
router.push('/');
|
||||||
|
|
||||||
/*if (this.$route.query.redirect) {
|
/*if (this.$route.query.redirect) {
|
||||||
router.push({path: this.$route.query.redirect});
|
router.push({path: this.$route.query.redirect});
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,33 +94,30 @@ export default createStore({
|
||||||
actions: {
|
actions: {
|
||||||
async login({commit, dispatch, state}, {username, password, remember}) {
|
async login({commit, dispatch, state}, {username, password, remember}) {
|
||||||
commit('setRemember', remember);
|
commit('setRemember', remember);
|
||||||
const data = await dispatch('apiLocalPost', {
|
const data = await fetch('/auth/token/', {
|
||||||
target: '/auth/token/', data: {
|
method: 'POST',
|
||||||
username: username, password: password
|
headers: {'Content-Type': 'application/json'},
|
||||||
}
|
body: JSON.stringify({username: username, password: password}),
|
||||||
})
|
credentials: 'omit'
|
||||||
|
}).then(r => r.json())
|
||||||
if (data.token) {
|
if (data.token) {
|
||||||
commit('setToken', data.token);
|
commit('setToken', data.token);
|
||||||
commit('setUser', username);
|
commit('setUser', username);
|
||||||
const j = await dispatch('apiLocalGet', {target: '/auth/keys/'})
|
const j = await fetch('/auth/keys/', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {'Authorization': 'Token ' + data.token},
|
||||||
|
credentials: 'omit'
|
||||||
|
}).then(r => r.json())
|
||||||
const k = j.key
|
const k = j.key
|
||||||
commit('setKey', k)
|
commit('setKey', k)
|
||||||
|
const s = await dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
|
||||||
|
commit('setHomeServers', s)
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async getFriends({commit, dispatch, state}) {
|
async lookupServer({state}, {username}) {
|
||||||
const home_server = "localhost:8000"
|
|
||||||
const data = await dispatch('apiFederatedGet', {
|
|
||||||
host: home_server,
|
|
||||||
target: '/api/friends/'
|
|
||||||
})
|
|
||||||
console.log('getFriends', data)
|
|
||||||
commit('setFriends', data)
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
async getFriendServer({state}, {username}) {
|
|
||||||
const domain = username.split('@')[1]
|
const domain = username.split('@')[1]
|
||||||
if (domain === 'example.eleon')
|
if (domain === 'example.eleon')
|
||||||
return ['10.23.42.186:8000'];
|
return ['10.23.42.186:8000'];
|
||||||
|
@ -126,7 +134,31 @@ export default createStore({
|
||||||
(result) => result.map(
|
(result) => result.map(
|
||||||
(answer) => answer.target + ':' + answer.port))
|
(answer) => answer.target + ':' + answer.port))
|
||||||
},
|
},
|
||||||
async apiFederatedGet({state}, {host, target}) {
|
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 getAllFriendsServers({state, dispatch, commit}) {
|
||||||
|
if (state.all_friends_servers)
|
||||||
|
return state.all_friends_servers
|
||||||
|
const promise = (async () => {
|
||||||
|
const servers = new ServerSet([], state.unreachable_neighbors)
|
||||||
|
for (const friend of state.friends) {
|
||||||
|
const s = await dispatch('lookupServer', {username: friend})
|
||||||
|
servers.add(s)
|
||||||
|
}
|
||||||
|
return servers
|
||||||
|
})()
|
||||||
|
commit('setAllFriendsServers', promise)
|
||||||
|
return promise
|
||||||
|
},
|
||||||
|
async getFriendServers({state, dispatch, commit}, {username}) {
|
||||||
|
return dispatch('lookupServer', {username}).then(servers => new ServerSet(servers, state.unreachable_neighbors))
|
||||||
|
},
|
||||||
|
/*async apiFederatedGet({state}, {host, target}) {
|
||||||
if (state.unreachable_neighbors.queryUnreachable(host)) {
|
if (state.unreachable_neighbors.queryUnreachable(host)) {
|
||||||
throw new Error('unreachable neighbor')
|
throw new Error('unreachable neighbor')
|
||||||
}
|
}
|
||||||
|
@ -185,73 +217,99 @@ export default createStore({
|
||||||
credentials: 'omit',
|
credentials: 'omit',
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data)
|
||||||
}).then(response => response.json())
|
}).then(response => response.json())
|
||||||
|
},*/
|
||||||
|
async fetchInventoryItems({commit, dispatch, getters}) {
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
const items = await servers.get(getters.signAuth, '/api/inventory_items/')
|
||||||
|
commit('setInventoryItems', {url: '/', items})
|
||||||
|
return items
|
||||||
},
|
},
|
||||||
async requestFriend({state, dispatch}, {username}) {
|
async searchInventories({state, dispatch, getters}, {query}) {
|
||||||
|
const servers = await dispatch('getAllFriendsServers')
|
||||||
|
return await servers.get(getters.signAuth, '/api/inventory/search/?q=' + query)
|
||||||
|
},
|
||||||
|
/*async searchInventoryItems() {
|
||||||
|
try {
|
||||||
|
const servers = await this.fetchFriends().then(friends => friends.map(friend => this.lookupServer({username: friend.name})))
|
||||||
|
const urls = servers.map(server => server.then(s => {
|
||||||
|
return {host: s, target: "/api/inventory_items/"}
|
||||||
|
}))
|
||||||
|
urls.map(url => url.then(u => this.apiFederatedGet(u).then(items => {
|
||||||
|
this.setInventoryItems({url: u.domain, items})
|
||||||
|
}).catch(e => {
|
||||||
|
}))) // TODO: handle error
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
},*/
|
||||||
|
async fetchFriends({commit, dispatch, getters, state}) {
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
const data = await servers.get(getters.signAuth, '/api/friends/')
|
||||||
|
commit('setFriends', data)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
async fetchFriendRequests({state, dispatch, getters}) {
|
||||||
|
const servers = await dispatch('getHomeServers')
|
||||||
|
return await servers.get(getters.signAuth, '/api/friendrequests/')
|
||||||
|
},
|
||||||
|
async requestFriend({state, dispatch, getters}, {username}) {
|
||||||
console.log('requesting friend ' + username)
|
console.log('requesting friend ' + username)
|
||||||
if (username in state.friends) {
|
if (username in state.friends) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
state.home_server = 'localhost:8000'
|
const home_servers = await dispatch('getHomeServers')
|
||||||
const home_reply = await dispatch('apiFederatedPost', {
|
const home_reply = home_servers.post(getters.signAuth, '/api/friendrequests/', {
|
||||||
host: state.home_server,
|
befriender: state.user,
|
||||||
target: '/api/friendrequests/',
|
befriendee: username
|
||||||
data: {befriender: state.user, befriendee: username}
|
|
||||||
})
|
})
|
||||||
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)
|
console.log('home_reply', home_reply)
|
||||||
const befriendee_server = await dispatch('getFriendServer', {username})
|
const befriendee_servers = await dispatch('getFriendServers', {username})
|
||||||
const ext_reply = await dispatch('apiFederatedPost', {
|
const ext_reply = befriendee_servers.post(getters.signAuth, '/api/friendrequests/', {
|
||||||
host: befriendee_server[0],
|
|
||||||
target: '/api/friendrequests/',
|
|
||||||
data: {
|
|
||||||
befriender: state.user,
|
befriender: state.user,
|
||||||
befriendee: username,
|
befriendee: username,
|
||||||
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)
|
console.log('ext_reply', ext_reply)
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
async acceptFriend({state, dispatch}, {id, secret, befriender}) {
|
async acceptFriend({state, dispatch, getters}, {id, secret, befriender}) {
|
||||||
console.log('accepting friend ' + id)
|
console.log('accepting friend ' + id)
|
||||||
state.home_server = 'localhost:8000'
|
const home_servers = await dispatch('getHomeServers')
|
||||||
const home_reply = await dispatch('apiFederatedPost', {
|
const home_reply = await home_servers.post(getters.signAuth, '/api/friends/', {
|
||||||
host: state.home_server,
|
|
||||||
target: '/api/friends/',
|
|
||||||
data: {
|
|
||||||
friend_request_id: id, secret: secret
|
friend_request_id: id, secret: secret
|
||||||
}
|
|
||||||
})
|
})
|
||||||
console.log('home_reply', home_reply)
|
console.log('home_reply', home_reply)
|
||||||
const ext_server = await dispatch('getFriendServer', {username: befriender})
|
const ext_servers = await dispatch('getFriendServers', {username: befriender})
|
||||||
const ext_reply = await dispatch('apiFederatedPost', {
|
const ext_reply = await ext_servers.post(getters.signAuth, '/api/friendrequests/', {
|
||||||
host: ext_server[0],
|
|
||||||
target: '/api/friendrequests/',
|
|
||||||
data: {
|
|
||||||
befriender: state.user,
|
befriender: state.user,
|
||||||
befriendee: befriender,
|
befriendee: befriender,
|
||||||
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)
|
console.log('ext_reply', ext_reply)
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
async declineFriend({state, dispatch}, args) {
|
async declineFriend({state, dispatch}, args) {
|
||||||
|
// TODO implement
|
||||||
console.log('declining friend ' + args)
|
console.log('declining friend ' + args)
|
||||||
},
|
},
|
||||||
async fetchFriendRequests({state, dispatch}) {
|
|
||||||
const requests = await dispatch('apiLocalGet', {target: '/api/friendrequests/'})
|
|
||||||
return requests
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
isLoggedIn(state) {
|
isLoggedIn(state) {
|
||||||
return state.user !== null && state.token !== null;
|
return state.user !== null && state.token !== null;
|
||||||
},
|
},
|
||||||
|
signAuth(state) {
|
||||||
|
console.log('signAuth', state.user, state.keypair.signSk)
|
||||||
|
return createSignAuth(state.user, state.keypair.signSk)
|
||||||
|
},
|
||||||
|
tokenAuth(state) {
|
||||||
|
console.log('tokenAuth', state.token)
|
||||||
|
return createTokenAuth(state.token)
|
||||||
|
},
|
||||||
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)
|
||||||
|
|
|
@ -78,7 +78,8 @@
|
||||||
<tr v-for="request in requests" :key="request.befriender">
|
<tr v-for="request in requests" :key="request.befriender">
|
||||||
<td>{{ request.befriender }}</td>
|
<td>{{ request.befriender }}</td>
|
||||||
<td class="d-none d-md-table-cell">
|
<td class="d-none d-md-table-cell">
|
||||||
{{ request.befriender_public_key.slice(0,32) }}...</td>
|
{{ request.befriender_public_key.slice(0, 32) }}...
|
||||||
|
</td>
|
||||||
<td class="table-action">
|
<td class="table-action">
|
||||||
<button class="btn btn-sm btn-success" @click="tryAcceptFriend(request)">
|
<button class="btn btn-sm btn-success" @click="tryAcceptFriend(request)">
|
||||||
<b-icon-check></b-icon-check>
|
<b-icon-check></b-icon-check>
|
||||||
|
@ -128,11 +129,11 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(['getFriends', "getFriendServer", "requestFriend", "acceptFriend", "fetchFriendRequests", "declineFriend"]),
|
...mapActions(['fetchFriends', "lookupServer", "requestFriend", "acceptFriend", "fetchFriendRequests", "declineFriend"]),
|
||||||
fetchContent() {
|
fetchContent() {
|
||||||
this.getFriends().then((friends) => {
|
this.fetchFriends().then((friends) => {
|
||||||
friends.map((friend) => {
|
friends.map((friend) => {
|
||||||
this.getFriendServer(friend).then((server) => {
|
this.lookupServer(friend).then((server) => {
|
||||||
this.friends[friend.username] = {...friend, server: server}
|
this.friends[friend.username] = {...friend, server: server}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -151,21 +152,24 @@ export default {
|
||||||
this.newfriend = ""
|
this.newfriend = ""
|
||||||
this.fetchContent()
|
this.fetchContent()
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {
|
||||||
|
})
|
||||||
},
|
},
|
||||||
tryAcceptFriend(request) {
|
tryAcceptFriend(request) {
|
||||||
this.acceptFriend({id: request.id, secret: request.secret, befriender: request.befriender}).then((ok) => {
|
this.acceptFriend({id: request.id, secret: request.secret, befriender: request.befriender}).then((ok) => {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
this.fetchContent()
|
this.fetchContent()
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {
|
||||||
|
})
|
||||||
},
|
},
|
||||||
tryRejectFriend(friend) {
|
tryRejectFriend(friend) {
|
||||||
this.declineFriend({username: friend}).then((ok) => {
|
this.declineFriend({username: friend}).then((ok) => {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
this.fetchContent()
|
this.fetchContent()
|
||||||
}
|
}
|
||||||
}).catch(() => {})
|
}).catch(() => {
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<button class="btn" @click="getInventoryItems">Refresh</button>
|
<button class="btn" @click="fetchInventoryItems">Refresh</button>
|
||||||
<router-link to="/inventory/new" class="btn btn-primary">Add</router-link>
|
<router-link to="/inventory/new" class="btn btn-primary">Add</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,25 +67,10 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(["apiFederatedGet", "getFriends", "getFriendServer"]),
|
...mapActions(["fetchInventoryItems"]),
|
||||||
...mapMutations(["setInventoryItems"]),
|
|
||||||
async getInventoryItems() {
|
|
||||||
try {
|
|
||||||
const servers = await this.getFriends().then(friends => friends.map(friend => this.getFriendServer({username: friend})))
|
|
||||||
const urls = servers.map(server => server.then(s => {
|
|
||||||
return {host: s, target: "/api/inventory_items/"}
|
|
||||||
}))
|
|
||||||
urls.map(url => url.then(u => this.apiFederatedGet(u).then(items => {
|
|
||||||
this.setInventoryItems({url: u.domain, items})
|
|
||||||
}).catch(e => {
|
|
||||||
}))) // TODO: handle error
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
await this.getInventoryItems()
|
await this.fetchInventoryItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue