diff --git a/backend/.env.dist b/backend/.env.dist
index 24a8a42..4cf4790 100644
--- a/backend/.env.dist
+++ b/backend/.env.dist
@@ -1,2 +1,4 @@
-ALLOWED_HOSTS="localhost,127.0.0.1"
\ No newline at end of file
+ALLOWED_HOSTS="localhost,127.0.0.1"
+
+SERVE_X_ACCEL_REDIRECT=True
\ No newline at end of file
diff --git a/backend/backend/settings.py b/backend/backend/settings.py
index 82f2e67..1d09378 100644
--- a/backend/backend/settings.py
+++ b/backend/backend/settings.py
@@ -86,6 +86,8 @@ CORS_ALLOW_ALL_ORIGINS = True
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+SERVE_X_ACCEL_REDIRECT = os.environ.get('SERVE_X_ACCEL_REDIRECT', 'False').lower() == 'true'
+
ROOT_URLCONF = 'backend.urls'
TEMPLATES = [
diff --git a/backend/files/media_urls.py b/backend/files/media_urls.py
index aca590d..b87ab00 100644
--- a/backend/files/media_urls.py
+++ b/backend/files/media_urls.py
@@ -7,6 +7,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from authentication.signature_auth import SignatureAuthentication
+from backend import settings
from files.models import File
@@ -19,12 +20,21 @@ def media_urls(request, hash_path):
file = File.objects.filter(connected_items__owner__in=request.user.friends_or_self()).distinct().get(
file=hash_path)
- return HttpResponse(status=status.HTTP_200_OK,
- content_type=file.mime_type,
- headers={
- 'X-Accel-Redirect': f'/redirect_media/{hash_path}',
- 'Access-Control-Allow-Origin': '*',
- }) # TODO Expires and Cache-Control
+ if settings.SERVE_X_ACCEL_REDIRECT:
+ return HttpResponse(status=status.HTTP_200_OK,
+ content_type=file.mime_type,
+ headers={
+ 'X-Accel-Redirect': f'/redirect_media/{hash_path}',
+ 'Access-Control-Allow-Origin': '*',
+ }) # TODO Expires and Cache-Control
+ else:
+ return HttpResponse(status=status.HTTP_200_OK,
+ content_type=file.mime_type,
+ headers={
+ 'Access-Control-Allow-Origin': '*',
+ },
+ content=open(file.file.path, 'rb').read())
+
except File.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
diff --git a/deploy/dev/instance_a/a.env b/deploy/dev/instance_a/a.env
index 33c5b7e..5816f2a 100644
--- a/deploy/dev/instance_a/a.env
+++ b/deploy/dev/instance_a/a.env
@@ -2,6 +2,8 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG=True
+SERVE_X_ACCEL_REDIRECT=False
+
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY='e*lm&*!j0_stqaiod$1zob(vs@aq6+n-i$1%!rek)_v9n^ue$3'
diff --git a/deploy/dev/instance_a/nginx-a.dev.conf b/deploy/dev/instance_a/nginx-a.dev.conf
index b77f550..37f4f1b 100644
--- a/deploy/dev/instance_a/nginx-a.dev.conf
+++ b/deploy/dev/instance_a/nginx-a.dev.conf
@@ -45,6 +45,16 @@ http {
proxy_pass http://backend;
}
+ location /media {
+ proxy_set_header Host $host:$server_port;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Forwarded-Host $host:$server_port;
+ proxy_set_header X-Forwarded-Port $server_port;
+ proxy_pass http://backend;
+ }
+
location /docs {
proxy_pass http://backend/docs;
}
diff --git a/deploy/dev/instance_b/b.env b/deploy/dev/instance_b/b.env
index c0118ca..76e8087 100644
--- a/deploy/dev/instance_b/b.env
+++ b/deploy/dev/instance_b/b.env
@@ -1,6 +1,8 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG=True
+SERVE_X_ACCEL_REDIRECT=True
+
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY='7ccxjje%q@@0*z+r&-$fy3(rj9n)%$!sk-k++-&rb=_u(wpjbe'
diff --git a/frontend/src/components/AuthenticatedImage.vue b/frontend/src/components/AuthenticatedImage.vue
new file mode 100644
index 0000000..5f3f0f3
--- /dev/null
+++ b/frontend/src/components/AuthenticatedImage.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/CameraFileSource.vue b/frontend/src/components/CameraFileSource.vue
new file mode 100644
index 0000000..5a93a89
--- /dev/null
+++ b/frontend/src/components/CameraFileSource.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/CombinedFileField.vue b/frontend/src/components/CombinedFileField.vue
new file mode 100644
index 0000000..c5430b0
--- /dev/null
+++ b/frontend/src/components/CombinedFileField.vue
@@ -0,0 +1,193 @@
+
+
+ Create New
+ Update
+
+ -
+ {{ file.name }}
+
+ -
+ {{ file.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/DeletableWrapper.vue b/frontend/src/components/DeletableWrapper.vue
new file mode 100644
index 0000000..d357a01
--- /dev/null
+++ b/frontend/src/components/DeletableWrapper.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/DragDropFileSource.vue b/frontend/src/components/DragDropFileSource.vue
new file mode 100644
index 0000000..6646ef5
--- /dev/null
+++ b/frontend/src/components/DragDropFileSource.vue
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/FsFileSource.vue b/frontend/src/components/FsFileSource.vue
new file mode 100644
index 0000000..29bff9a
--- /dev/null
+++ b/frontend/src/components/FsFileSource.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/WebcamFileSource.vue b/frontend/src/components/WebcamFileSource.vue
new file mode 100644
index 0000000..3605a79
--- /dev/null
+++ b/frontend/src/components/WebcamFileSource.vue
@@ -0,0 +1,201 @@
+
+
+
+
+
+
+
+
+ {{ lastError }}
+
+
+
![Image not available.]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/store.js b/frontend/src/store.js
index 0cadaff..b0f43e5 100644
--- a/frontend/src/store.js
+++ b/frontend/src/store.js
@@ -77,6 +77,9 @@ export default createStore({
setDomains(state, domains) {
state.domains = domains;
},
+ setFiles(state, files) {
+ state.files = files;
+ },
logout(state) {
state.user = null;
state.token = null;
@@ -232,6 +235,40 @@ export default createStore({
const servers = await dispatch('getHomeServers')
return await servers.delete(getters.signAuth, '/api/friends/' + id + '/')
},
+ async fetchFiles({state, commit, dispatch, getters}) {
+ if (state.last_load.files > Date.now() - 1000 * 60 * 60 * 24) {
+ return state.files
+ }
+ const servers = await dispatch('getHomeServers')
+ const data = await servers.get(getters.signAuth, '/api/files/')
+ data.map(file => file.owner = state.user)
+ commit('setFiles', data)
+ state.last_load.files = Date.now()
+ return data
+ },
+ async fetchFilesByItem({state, commit, dispatch, getters}, {id}) {
+ const servers = await dispatch('getHomeServers')
+ return await servers.get(getters.signAuth, '/api/item_files/' + id + '/')
+ },
+ async pushFile({state, dispatch, getters}, {item_id, file}) {
+ const servers = await dispatch('getHomeServers')
+ const data = await servers.post(getters.signAuth, '/api/item_files/' + item_id + '/', file)
+ if (data.name) {
+ data.owner = state.user
+ state.files.push(data)
+ return data
+ }
+ },
+ async deleteFile({state, dispatch, getters}, {id}) {
+ const servers = await dispatch('getHomeServers')
+ await servers.delete(getters.signAuth, '/api/files/' + id + '/')
+ state.files = state.files.filter(file => file.id !== id)
+ },
+ async deleteItemFile({state, dispatch, getters}, {item_id, file_id}) {
+ const servers = await dispatch('getHomeServers')
+ await servers.delete(getters.signAuth, '/api/item_files/' + item_id + '/' + file_id + '/')
+ state.files = state.files.filter(file => file.id !== file_id)
+ },
async fetchTags({state, commit, dispatch, getters}) {
if (state.last_load.tags > Date.now() - 1000 * 60 * 60 * 24) {
return state.tags
diff --git a/frontend/src/views/Inventory.vue b/frontend/src/views/Inventory.vue
index 8687fa6..da488b9 100644
--- a/frontend/src/views/Inventory.vue
+++ b/frontend/src/views/Inventory.vue
@@ -48,24 +48,27 @@
-
![]()
+
![]()
+
-
{{ item.name }}
+
+
+ {{ item.name }}
+
{{ item.availability_policy }}
{{ item.owned_quantity }}
-
+
@@ -89,6 +92,7 @@
import {mapActions, mapGetters, mapMutations, mapState} from "vuex";
import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue";
+import AuthenticatedImage from "../components/AuthenticatedImage.vue";
export default {
name: "Inventory",
@@ -98,6 +102,7 @@ export default {
}
},
components: {
+ AuthenticatedImage,
BaseLayout,
...BIcons
},
@@ -107,6 +112,9 @@ export default {
},
methods: {
...mapActions(["fetchInventoryItems", "deleteInventoryItem"]),
+ only_images(files) {
+ return files.filter(file => file.mime_type.startsWith("image/"));
+ },
},
async mounted() {
await this.fetchInventoryItems()
@@ -115,5 +123,7 @@ export default {
\ No newline at end of file
diff --git a/frontend/src/views/InventoryDetail.vue b/frontend/src/views/InventoryDetail.vue
index ab8910b..9c55894 100644
--- a/frontend/src/views/InventoryDetail.vue
+++ b/frontend/src/views/InventoryDetail.vue
@@ -26,6 +26,14 @@
{{ item.owned_quantity }}
+
@@ -50,10 +58,15 @@
import * as BIcons from "bootstrap-icons-vue";
import BaseLayout from "@/components/BaseLayout.vue";
import {mapActions, mapGetters} from "vuex";
+import AuthenticatedImage from "@/components/AuthenticatedImage.vue";
+import PropertyField from "../components/PropertyField.vue";
+import TagField from "../components/TagField.vue";
export default {
name: "InventoryDetail",
components: {
+ AuthenticatedImage,
+ PropertyField, TagField,
BaseLayout,
...BIcons
},
diff --git a/frontend/src/views/InventoryEdit.vue b/frontend/src/views/InventoryEdit.vue
index 4e7220a..06163b1 100644
--- a/frontend/src/views/InventoryEdit.vue
+++ b/frontend/src/views/InventoryEdit.vue
@@ -39,6 +39,11 @@
+
+
+
+
+
+
+
+