stash
This commit is contained in:
parent
6d2167ac66
commit
4787acd8eb
23 changed files with 1241 additions and 25 deletions
|
|
@ -33,6 +33,12 @@
|
|||
<span class="align-middle">Friends</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="sidebar-item">
|
||||
<router-link to="/groups" class="sidebar-link">
|
||||
<b-icon-people-fill class="bi-valign-middle"></b-icon-people-fill>
|
||||
<span class="align-middle">Groups</span>
|
||||
</router-link>
|
||||
</li>
|
||||
<li class="sidebar-item">
|
||||
<router-link to="/files" class="sidebar-link">
|
||||
<b-icon-folder class="bi-valign-middle"></b-icon-folder>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import store from '@/store';
|
|||
import Profile from '@/views/Profile.vue';
|
||||
import Settings from '@/views/Settings.vue';
|
||||
import Friends from '@/views/Friends.vue';
|
||||
import Groups from '@/views/Groups.vue';
|
||||
import GroupNew from '@/views/GroupNew.vue';
|
||||
import GroupDetail from '@/views/GroupDetail.vue';
|
||||
import Inventory from '@/views/Inventory.vue';
|
||||
import Search from '@/views/Search.vue';
|
||||
import InventoryDetail from '@/views/InventoryDetail.vue';
|
||||
|
|
@ -62,6 +65,19 @@ const routes = [{path: '/', component: Dashboard, meta: {requiresAuth: true}}, {
|
|||
path: '/friends',
|
||||
component: Friends,
|
||||
meta: {requiresAuth: true}
|
||||
}, {
|
||||
path: '/groups',
|
||||
component: Groups,
|
||||
meta: {requiresAuth: true}
|
||||
}, {
|
||||
path: '/groups/new',
|
||||
component: GroupNew,
|
||||
meta: {requiresAuth: true}
|
||||
}, {
|
||||
path: '/groups/:id',
|
||||
component: GroupDetail,
|
||||
meta: {requiresAuth: true},
|
||||
props: true
|
||||
}, {path: '/files', component: Files, meta: {requiresAuth: true}
|
||||
}, {path: '/workflows',
|
||||
component: Workflows,
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ export default createStore({
|
|||
remember: false,
|
||||
friends: [],
|
||||
friendProfiles: {},
|
||||
groups: [],
|
||||
groupInvites: [],
|
||||
item_map: {},
|
||||
home_servers: null,
|
||||
all_friends_servers: null,
|
||||
|
|
@ -104,6 +106,12 @@ export default createStore({
|
|||
setFriendProfiles(state, profiles) {
|
||||
state.friendProfiles = profiles;
|
||||
},
|
||||
setGroups(state, groups) {
|
||||
state.groups = groups;
|
||||
},
|
||||
setGroupInvites(state, invites) {
|
||||
state.groupInvites = invites;
|
||||
},
|
||||
setHomeServers(state, home_servers) {
|
||||
state.home_servers = home_servers;
|
||||
},
|
||||
|
|
@ -479,6 +487,75 @@ export default createStore({
|
|||
const servers = await dispatch('getHomeServers')
|
||||
return await servers.delete(getters.signAuth, '/api/friends/' + id + '/')
|
||||
},
|
||||
// Groups are only ever hosted on the current user's own home backend for now (see
|
||||
// docs/design-in-progress/groups-mvp.md) - a remote member's edit/delete rights on a
|
||||
// group-owned item work regardless, but "My Groups" has no way to discover a group hosted
|
||||
// elsewhere, so every group action below talks to getHomeServers rather than resolving a
|
||||
// per-group domain.
|
||||
async fetchGroups({commit, dispatch, getters}) {
|
||||
const servers = await dispatch('getHomeServers')
|
||||
const data = await servers.get(getters.signAuth, '/api/groups/')
|
||||
commit('setGroups', data)
|
||||
return data
|
||||
},
|
||||
async fetchGroup({dispatch, getters}, {id}) {
|
||||
const servers = await dispatch('getHomeServers')
|
||||
return await servers.get(getters.signAuth, '/api/groups/' + id + '/')
|
||||
},
|
||||
async createGroup({dispatch, getters}, {name}) {
|
||||
const servers = await dispatch('getHomeServers')
|
||||
return await servers.post(getters.signAuth, '/api/groups/', {name})
|
||||
},
|
||||
async removeGroupMember({dispatch, getters}, {groupId, identityId}) {
|
||||
const servers = await dispatch('getHomeServers')
|
||||
return await servers.delete(getters.signAuth, '/api/groups/' + groupId + '/members/' + identityId + '/')
|
||||
},
|
||||
async fetchGroupInvites({commit, dispatch, getters}) {
|
||||
const servers = await dispatch('getHomeServers')
|
||||
const data = await servers.get(getters.signAuth, '/api/groupinvites/')
|
||||
commit('setGroupInvites', data)
|
||||
return data
|
||||
},
|
||||
async inviteToGroup({state, dispatch, getters}, {groupId, groupHandle, invitee}) {
|
||||
const home_servers = await dispatch('getHomeServers')
|
||||
const home_reply = await home_servers.post(
|
||||
getters.signAuth, '/api/groups/' + groupId + '/invites/', {invitee})
|
||||
if (!home_reply.secret) {
|
||||
return false
|
||||
}
|
||||
const invitee_servers = await dispatch('getFriendServers', {username: invitee})
|
||||
await invitee_servers.post(getters.signAuth, '/api/groupinvites/', {
|
||||
group: groupHandle,
|
||||
inviter: state.user,
|
||||
inviter_key: nacl.to_hex(state.keypair.signPk),
|
||||
invitee,
|
||||
secret: home_reply.secret
|
||||
})
|
||||
return true
|
||||
},
|
||||
async acceptGroupInvite({state, dispatch, getters}, invite) {
|
||||
const group_domain = invite.group.split('@')[1]
|
||||
const group_servers = await dispatch('getFriendServers', {username: 'x@' + group_domain})
|
||||
await group_servers.post(getters.signAuth, '/api/group_invites/accept/', {
|
||||
group: invite.group,
|
||||
invitee: state.user,
|
||||
invitee_key: nacl.to_hex(state.keypair.signPk),
|
||||
secret: invite.secret
|
||||
})
|
||||
const home_servers = await dispatch('getHomeServers')
|
||||
await home_servers.delete(getters.signAuth, '/api/groupinvites/' + invite.id + '/')
|
||||
return true
|
||||
},
|
||||
async declineGroupInvite({dispatch, getters}, invite) {
|
||||
const servers = await dispatch('getHomeServers')
|
||||
return await servers.delete(getters.signAuth, '/api/groupinvites/' + invite.id + '/')
|
||||
},
|
||||
async fetchGroupInventoryItems({commit, dispatch, getters}, groupId) {
|
||||
const servers = await dispatch('getHomeServers')
|
||||
const items = await servers.get(getters.signAuth, '/api/inventory_items/?group=' + groupId)
|
||||
commit('setInventoryItems', {url: '/group/' + groupId, items})
|
||||
return items
|
||||
},
|
||||
async fetchFiles({state, commit, dispatch, getters}) {
|
||||
if (state.last_load.files > Date.now() - 1000 * 60 * 60 * 24) {
|
||||
return state.files
|
||||
|
|
@ -708,6 +785,7 @@ export default createStore({
|
|||
inventory_items(state) {
|
||||
return state.item_map['/'] || []
|
||||
},
|
||||
groupInventoryItems: (state) => (groupId) => state.item_map['/group/' + groupId] || [],
|
||||
loaded_items(state) {
|
||||
return Object.entries(state.item_map).reduce((acc, [url, items]) => {
|
||||
return acc.concat(items)
|
||||
|
|
|
|||
207
frontend/src/views/GroupDetail.vue
Normal file
207
frontend/src/views/GroupDetail.vue
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
<template>
|
||||
<BaseLayout>
|
||||
<main class="content">
|
||||
<div class="container-fluid p-0">
|
||||
<h1 class="h3 mb-3">{{ group ? group.handle : '' }}</h1>
|
||||
<div class="row" v-if="group">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Members</h5>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th style="width: 16em">
|
||||
<a @click="refresh" class="align-middle">
|
||||
<b-icon-arrow-clockwise></b-icon-arrow-clockwise>
|
||||
Refresh
|
||||
</a>
|
||||
<a @click="show_invite = true" class="align-middle">
|
||||
<b-icon-plus></b-icon-plus>
|
||||
Add member
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="show_invite">
|
||||
<td>
|
||||
<input type="text" class="form-control" placeholder="user@domain"
|
||||
v-model="invitee">
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-primary" @click="tryInvite">Invite</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="member in group.members" :key="member.id">
|
||||
<td>
|
||||
<user-name-tag :user="member"/>
|
||||
</td>
|
||||
<td class="table-action">
|
||||
<a href="#" class="align-middle" @click.prevent="tryRemoveMember(member)">
|
||||
<b-icon-trash></b-icon-trash>
|
||||
Remove
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Group inventory</h5>
|
||||
<button v-if="layout === 'grid'" @click="layout = 'table'" class="btn">
|
||||
<b-icon-list></b-icon-list>
|
||||
</button>
|
||||
<button v-else @click="layout = 'grid'" class="btn">
|
||||
<b-icon-grid></b-icon-grid>
|
||||
</button>
|
||||
</div>
|
||||
<table class="table table-striped" v-if="layout === 'table'">
|
||||
<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 items" :key="item.id">
|
||||
<td>
|
||||
<router-link :to="`/inventory/${item.id}`">{{ item.name }}</router-link>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">
|
||||
<span class="badge bg-secondary text-white">{{ item.availability_policy }}</span>
|
||||
</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="tryDeleteItem(item)">
|
||||
<b-icon-trash></b-icon-trash>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card-body" v-else>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-4 col-lg-3 col-xl-2" v-for="item in items" :key="item.id">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-0">
|
||||
<router-link :to="`/inventory/${item.id}`">
|
||||
{{ item.name }}
|
||||
</router-link>
|
||||
</h5>
|
||||
<div class="card-text text-black-50">
|
||||
<span class="badge bg-secondary text-white">{{ item.availability_policy }}</span>
|
||||
<span class="float-right">{{ item.owned_quantity }}</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-danger btn-sm" @click="tryDeleteItem(item)">
|
||||
Delete
|
||||
</button>
|
||||
<router-link :to="`/inventory/${item.id}/edit`"
|
||||
class="btn btn-primary btn-sm">Edit
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<button class="btn" @click="fetchItems">Refresh</button>
|
||||
<router-link :to="`/inventory/new?group=${id}`" class="btn btn-primary">Add</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapGetters, mapState} from "vuex";
|
||||
import * as BIcons from "bootstrap-icons-vue";
|
||||
import BaseLayout from "@/components/BaseLayout.vue";
|
||||
import UserNameTag from "@/components/UserNameTag.vue";
|
||||
|
||||
export default {
|
||||
name: 'GroupDetail',
|
||||
components: {
|
||||
BaseLayout,
|
||||
UserNameTag,
|
||||
...BIcons
|
||||
},
|
||||
props: {
|
||||
id: {
|
||||
type: [String, Number],
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
group: null,
|
||||
layout: "grid",
|
||||
show_invite: false,
|
||||
invitee: ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['user']),
|
||||
items() {
|
||||
return this.groupInventoryItems(this.id)
|
||||
},
|
||||
...mapGetters(['groupInventoryItems'])
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchGroup', 'removeGroupMember', 'inviteToGroup', 'fetchGroupInventoryItems',
|
||||
'deleteInventoryItem']),
|
||||
refresh() {
|
||||
this.fetchGroup({id: this.id}).then((group) => {
|
||||
this.group = group
|
||||
})
|
||||
},
|
||||
fetchItems() {
|
||||
this.fetchGroupInventoryItems(this.id)
|
||||
},
|
||||
tryInvite() {
|
||||
this.inviteToGroup({groupId: this.id, groupHandle: this.group.handle, invitee: this.invitee})
|
||||
.then((ok) => {
|
||||
if (ok) {
|
||||
this.show_invite = false
|
||||
this.invitee = ""
|
||||
}
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
tryRemoveMember(member) {
|
||||
this.removeGroupMember({groupId: this.id, identityId: member.id}).then(() => {
|
||||
this.refresh()
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
tryDeleteItem(item) {
|
||||
this.deleteInventoryItem(item).then(() => {
|
||||
this.fetchItems()
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.refresh()
|
||||
this.fetchItems()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
52
frontend/src/views/GroupNew.vue
Normal file
52
frontend/src/views/GroupNew.vue
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<BaseLayout>
|
||||
<main class="content">
|
||||
<div class="container-fluid p-0">
|
||||
<h1 class="h3 mb-3">New group</h1>
|
||||
<div class="row">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input type="text" class="form-control" v-model="name">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary" style="width: 100%"
|
||||
@click="tryCreateGroup">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions} from "vuex";
|
||||
import BaseLayout from "@/components/BaseLayout.vue";
|
||||
|
||||
export default {
|
||||
name: 'GroupNew',
|
||||
components: {
|
||||
BaseLayout
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['createGroup']),
|
||||
tryCreateGroup() {
|
||||
this.createGroup({name: this.name}).then((group) => {
|
||||
this.$router.push(`/groups/${group.id}`)
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
122
frontend/src/views/Groups.vue
Normal file
122
frontend/src/views/Groups.vue
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<BaseLayout>
|
||||
<main class="content">
|
||||
<div class="container-fluid p-0">
|
||||
<h1 class="h3 mb-3">Groups</h1>
|
||||
<div class="row">
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">My Groups</h5>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th class="d-none d-md-table-cell" style="width:25%">Members</th>
|
||||
<th style="width: 16em">
|
||||
<a @click="fetchGroups" class="align-middle">
|
||||
<b-icon-arrow-clockwise></b-icon-arrow-clockwise>
|
||||
Refresh
|
||||
</a>
|
||||
<router-link to="/groups/new" class="align-middle">
|
||||
<b-icon-plus></b-icon-plus>
|
||||
New group
|
||||
</router-link>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="group in groups" :key="group.id">
|
||||
<td>
|
||||
<router-link :to="`/groups/${group.id}`">{{ group.handle }}</router-link>
|
||||
</td>
|
||||
<td class="d-none d-md-table-cell">{{ group.members.length }}</td>
|
||||
<td class="table-action"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title">Open group invites</h5>
|
||||
</div>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group</th>
|
||||
<th class="d-none d-md-table-cell" style="width:25%">Invited by</th>
|
||||
<th style="width: 16em">
|
||||
<a @click="fetchGroupInvites" class="align-middle">
|
||||
<b-icon-arrow-clockwise></b-icon-arrow-clockwise>
|
||||
Refresh
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="invite in groupInvites" :key="invite.id">
|
||||
<td>{{ invite.group }}</td>
|
||||
<td class="d-none d-md-table-cell">{{ invite.inviter }}</td>
|
||||
<td class="table-action">
|
||||
<button class="btn btn-sm btn-success" @click="tryAcceptInvite(invite)">
|
||||
<b-icon-check></b-icon-check>
|
||||
Accept
|
||||
</button>
|
||||
<button class="btn btn-sm btn-danger" @click="tryDeclineInvite(invite)">
|
||||
<b-icon-x></b-icon-x>
|
||||
Decline
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapState} from "vuex";
|
||||
import * as BIcons from "bootstrap-icons-vue";
|
||||
import BaseLayout from "@/components/BaseLayout.vue";
|
||||
|
||||
export default {
|
||||
name: 'Groups',
|
||||
components: {
|
||||
BaseLayout,
|
||||
...BIcons
|
||||
},
|
||||
computed: {
|
||||
...mapState(['groups', 'groupInvites']),
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['fetchGroups', 'fetchGroupInvites', 'acceptGroupInvite', 'declineGroupInvite']),
|
||||
tryAcceptInvite(invite) {
|
||||
this.acceptGroupInvite(invite).then(() => {
|
||||
this.fetchGroupInvites()
|
||||
this.fetchGroups()
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
tryDeclineInvite(invite) {
|
||||
this.declineGroupInvite(invite).then(() => {
|
||||
this.fetchGroupInvites()
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchGroups()
|
||||
this.fetchGroupInvites()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
|
|
@ -92,7 +92,7 @@ export default {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(["inventory_items"]),
|
||||
...mapGetters(["loaded_items"]),
|
||||
...mapState(["availability_policies", "storage_locations"]),
|
||||
item() {
|
||||
return {
|
||||
|
|
@ -104,14 +104,14 @@ export default {
|
|||
image: "",
|
||||
files: [],
|
||||
storage_location: null,
|
||||
...this.inventory_items.find(item => item.id === parseInt(this.id))
|
||||
...this.loaded_items.find(item => item.id === parseInt(this.id))
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(["fetchInventoryItems", "updateInventoryItem", "fetchInfo", "fetchStorageLocations"]),
|
||||
changeFiles(files) {
|
||||
this.inventory_items.find(item => item.id === parseInt(this.id)).files = files
|
||||
this.loaded_items.find(item => item.id === parseInt(this.id)).files = files
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,15 @@
|
|||
<label for="property" class="form-label">Property</label>
|
||||
<property-field :value="item.properties"></property-field>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="owner" class="form-label">Owner</label>
|
||||
<select class="form-select" id="owner" name="owner" v-model="item.owner_group">
|
||||
<option :value="null">Myself</option>
|
||||
<option v-for="group in groups" :value="group.id" :key="group.id">
|
||||
{{ group.handle }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="quantity" class="form-label">Quantity</label>
|
||||
<input type="number" class="form-control" id="quantity" name="quantity"
|
||||
|
|
@ -96,19 +105,24 @@ export default {
|
|||
tags: [],
|
||||
properties: [],
|
||||
files: [],
|
||||
storage_location: null
|
||||
storage_location: null,
|
||||
owner_group: null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['createInventoryItem', 'fetchInfo', 'fetchStorageLocations'])
|
||||
...mapActions(['createInventoryItem', 'fetchInfo', 'fetchStorageLocations', 'fetchGroups'])
|
||||
},
|
||||
computed: {
|
||||
...mapState(["availability_policies", "storage_locations"]),
|
||||
...mapState(["availability_policies", "storage_locations", "groups"]),
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchInfo();
|
||||
await this.fetchStorageLocations();
|
||||
await this.fetchGroups();
|
||||
if (this.$route.query.group) {
|
||||
this.item.owner_group = parseInt(this.$route.query.group, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue