working on file browser

This commit is contained in:
Jonathan Baecker 2020-01-31 13:45:56 +01:00
parent 2623a6decf
commit 0510ba65c1
9 changed files with 190 additions and 10 deletions

View File

@ -1,11 +1,12 @@
import configparser import configparser
import os
from platform import uname from platform import uname
from time import sleep from time import sleep
from django.conf import settings from django.conf import settings
import psutil import psutil
from natsort import natsorted
def sizeof_fmt(num, suffix='B'): def sizeof_fmt(num, suffix='B'):
@ -117,8 +118,26 @@ class SystemStats:
} }
def set_root(path):
# prevent access to root file system
root = os.path.dirname(settings.MEDIA_FOLDER)
return path.lstrip(root)
def get_media_path(dir=None):
if not dir:
if not os.path.isdir(settings.MEDIA_FOLDER):
return ''
dir = settings.MEDIA_FOLDER
else:
dir = os.path.join(os.path.dirname(settings.MEDIA_FOLDER), dir)
for root, dirs, files in os.walk(dir, topdown=True):
return [set_root(root), natsorted(dirs), natsorted(files)]
if __name__ == '__main__': if __name__ == '__main__':
print('ALL: ', SystemStats().all()) result = hasattr(SystemStats(), 'system')
print(result)
exit() exit()
print('CPU: ', SystemStats.cpu()) print('CPU: ', SystemStats.cpu())
print('RAM: ', SystemStats.ram()) print('RAM: ', SystemStats.ram())

View File

@ -4,7 +4,7 @@ from django.conf import settings
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from .utils import IniParser, SystemStats from .utils import IniParser, SystemStats, get_media_path
class Config(APIView): class Config(APIView):
@ -35,8 +35,26 @@ class Statistics(APIView):
""" """
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'stats' in request.GET.dict() \ if 'stats' in request.GET.dict() and request.GET.dict()['stats'] \
and request.GET.dict()['stats'] == 'all': and hasattr(SystemStats(), request.GET.dict()['stats']):
return Response(SystemStats().all()) return Response(
getattr(SystemStats(), request.GET.dict()['stats'])())
else:
return Response({"success": False})
class Media(APIView):
"""
get folder/files tree, for building a file explorer
for reading, endpoint is: http://127.0.0.1:8000/api/media/?path
"""
def get(self, request, *args, **kwargs):
if 'path' in request.GET.dict() and request.GET.dict()['path']:
return Response({'tree': get_media_path(
request.GET.dict()['path']
)})
elif 'path' in request.GET.dict():
return Response({'tree': get_media_path()})
else: else:
return Response({"success": False}) return Response({"success": False})

View File

@ -157,3 +157,5 @@ FFPLAYOUT_CONFIG = '/etc/ffplayout/ffplayout.conf'
# used network interface for statistics: eth0, eno1, etc. # used network interface for statistics: eth0, eno1, etc.
NET_INTERFACE = 'br0' NET_INTERFACE = 'br0'
MEDIA_FOLDER = '/mnt/playout/ADtvMedia'

View File

@ -31,6 +31,7 @@ urlpatterns = [
path('api/', include(router.urls)), path('api/', include(router.urls)),
path('api/config/', views.Config.as_view()), path('api/config/', views.Config.as_view()),
path('api/stats/', views.Statistics.as_view()), path('api/stats/', views.Statistics.as_view()),
path('api/media/', views.Media.as_view()),
path('api-auth/', include( path('api-auth/', include(
'rest_framework.urls', namespace='rest_framework')), 'rest_framework.urls', namespace='rest_framework')),
path('auth/token/', TokenObtainPairView.as_view(), path('auth/token/', TokenObtainPairView.as_view(),

View File

@ -131,7 +131,9 @@
<div class="actions"> <div class="actions">
<b-button-group class="actions-grp"> <b-button-group class="actions-grp">
<b-button>Control</b-button> <b-button>Control</b-button>
<b-button to="/media">Media</b-button> <b-button to="/media">
Media
</b-button>
<b-button>Logging</b-button> <b-button>Logging</b-button>
<b-button>Configure</b-button> <b-button>Configure</b-button>
<b-button @click="logout()"> <b-button @click="logout()">

View File

@ -1,26 +1,111 @@
<template> <template>
<div> <div>
<b-container class="browser">
<div v-if="folderTree.tree" class="browser">
<div>
<b-breadcrumb>
<b-breadcrumb-item
v-for="(crumb, index) in crumbs"
:active="index === crumbs.length - 1"
:key="crumb.key"
@click="getPath(crumb.path)"
>
{{ crumb.text }}
</b-breadcrumb-item>
</b-breadcrumb>
</div>
<b-row>
<b-col class="folder-col">
<div class="browser-div">
<b-list-group>
<b-list-group-item
v-for="folder in folderTree.tree[1]"
:key="folder.key"
class="browser-item"
>
<b-link @click="getPath(`${folderTree.tree[0]}/${folder}`)">
{{ folder }}
</b-link>
</b-list-group-item>
</b-list-group>
</div>
</b-col>
<b-col class="files-col">
<div class="browser-div">
<b-list-group>
<b-list-group-item
v-for="file in folderTree.tree[2]"
:key="file.key"
class="browser-item"
>
<b-link>
{{ file }}
</b-link>
</b-list-group-item>
</b-list-group>
</div>
</b-col>
</b-row>
</div>
</b-container>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex'
export default { export default {
name: 'Media',
components: {}, components: {},
computed: {
...mapState('media', ['crumbs', 'folderTree'])
},
created () { created () {
this.getPath() this.getPath('')
}, },
methods: { methods: {
async getPath () { async getPath (path) {
await this.$store.dispatch('auth/inspectToken') await this.$store.dispatch('auth/inspectToken')
// const response = await this.$axios.get('apt/') await this.$store.dispatch('media/getTree', path)
} }
} }
} }
</script> </script>
<style> <style>
.browser {
width: 100%;
max-width: 100%;
}
.folder-col {
min-width: 320px;
max-width: 460px;
}
.folder {
padding: .3em;
}
.files-col {
min-width: 320px;
}
.browser-div {
background: #30363d;
height: 100%;
border: 1px solid #000;
border-radius: 5px;
}
.browser-item {
background: none;
padding: .3em;
border: none;
}
</style> </style>

View File

@ -0,0 +1,51 @@
export const state = () => ({
currentPath: null,
crumbs: [],
folderTree: {}
})
export const mutations = {
UPDATE_CURRENT_PATH (state, path) {
state.currentPath = path
},
UPDATE_CRUMBS (state, crumbs) {
state.crumbs = crumbs
},
UPDATE_FOLDER_TREE (state, tree) {
state.folderTree = tree
}
}
export const actions = {
async getTree ({ commit, dispatch, state, rootState }, path) {
if ((path !== state.currentPath || !state.folderTree)) {
const crumbs = []
let root = '/'
const response = await this.$axios.get(`api/media/?path=${path}`, { headers: { Authorization: 'Bearer ' + rootState.auth.jwtToken } })
if (response.data.tree) {
const pathArr = response.data.tree[0].split('/')
if (response.data.tree[1].length === 0) {
response.data.tree[1].push(pathArr[pathArr.length - 1])
}
if (path) {
for (const crumb of pathArr) {
if (crumb) {
root += crumb + '/'
crumbs.push({ text: crumb, path: root })
}
}
} else {
crumbs.push({ text: pathArr[pathArr.length - 1], path: '' })
}
// console.log(crumbs)
commit('UPDATE_CURRENT_PATH', path)
commit('UPDATE_CRUMBS', crumbs)
commit('UPDATE_FOLDER_TREE', response.data)
}
}
}
}

View File

@ -2,4 +2,5 @@ Django<=3.1
django-cors-headers django-cors-headers
djangorestframework djangorestframework
djangorestframework-simplejwt djangorestframework-simplejwt
natsort
psutil psutil

View File

@ -3,6 +3,7 @@ Django==3.0.2
django-cors-headers==3.2.1 django-cors-headers==3.2.1
djangorestframework==3.11.0 djangorestframework==3.11.0
djangorestframework-simplejwt==4.4.0 djangorestframework-simplejwt==4.4.0
natsort==7.0.1
psutil==5.6.7 psutil==5.6.7
PyJWT==1.7.1 PyJWT==1.7.1
pytz==2019.3 pytz==2019.3